Compare commits

...

200 Commits

Author SHA1 Message Date
metalgearsloth
914a7fb130 Fix box2 serializer (#1966)
There's some existing content with invalid bounds so this is going to be fun.
2021-08-24 18:30:42 +10:00
Pieter-Jan Briers
41d3495e29 Some stubs for JS execution queuing.
Definitely gonna be necessary but eeeeeeh feeling lazy.
2021-08-24 10:05:35 +02:00
Pieter-Jan Briers
2ae426b8e5 Allow file filters in save file dialogs. 2021-08-24 09:24:49 +02:00
Pieter-Jan Briers
eb45e16739 BrowserControl now allows hooking the BeforeBrowse API.
This is necessary to be able to cancel navigation (OpenDream Topic()).
2021-08-24 08:32:44 +02:00
Pieter-Jan Briers
79c952a871 Make MouseFilterMode.Stop default for BrowserControl 2021-08-24 08:01:22 +02:00
Pieter-Jan Briers
827c4b32fd Double check that CefManager is initialized in BrowserControl.
Otherwise it would throw with a nullref somewhere in CefGlue.
2021-08-24 07:35:28 +02:00
Pieter-Jan Briers
033e20cbf1 Add Closed (past tense) event to OSWindow.
Change a bunch of window event stuff up.
2021-08-24 04:49:04 +02:00
Pieter-Jan Briers
3ff46d40fa UsedImplicitly for IConsoleCommand 2021-08-24 04:42:09 +02:00
Pieter-Jan Briers
5ab6aba20b More implicit use attributes.
This means that entity systems and IoC managers should no longer need [UsedImplicitly] to suppress "object never instantiated" warnings.
2021-08-24 03:13:23 +02:00
Pieter-Jan Briers
e6be04cb83 OSWindow control to make OS windows much easier. 2021-08-24 02:05:25 +02:00
Pieter-Jan Briers
627b2f4d0d Change WindowCreateParameters.HideCloseButton to a more general purpose Styles system. 2021-08-24 02:05:25 +02:00
Pieter-Jan Briers
ffff42cc95 Add owner window parameter to window creation.
And also CenterOwner placement rule.

Currently owner windows are only implemented on Win32. Linux and macOS will need implementing still.
2021-08-24 02:05:25 +02:00
Pieter-Jan Briers
ce420ac8ab Set GLFW_SCALE_TO_MONITOR true on window creation. 2021-08-24 02:05:25 +02:00
Paul Ritter
d3cdfbe3cb ratio's rects (#1970)
Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2021-08-23 20:33:38 +02:00
Paul Ritter
726bfd58f6 adds some helpers to robustrandom (#1969)
Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2021-08-23 20:32:51 +02:00
Paul Ritter
05827d01d0 Datafield now implies readonly VVAccess (#1968)
Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2021-08-23 20:32:25 +02:00
metalgearsloth
6cb7641c07 Fix GetEntitiesInRange
A lot of lookup needs cleaning up but this is technically brokeded.
2021-08-23 22:40:47 +10:00
metalgearsloth
d72185933a Add support for grid chunk removals (#1941)
* Add support for grid chunk removals

Also allows grids to be removed when they have no more chunks remaining.

* No more crashing pog

* Slightly better

* Minor optimisations and fix bounds

* Avoid creating new chunks for anchoring

* chucky

* comment

* Tests

* Remove some logs

* Remove another log

* Review
2021-08-23 16:00:07 +10:00
metalgearsloth
80a9a82278 Fix client-side physics bodies getting stuck awake (#1946)
* Fix client-side physics bodies getting stuck awake

Wasn't a huge perf hit but it could leak a bit. Also there's a bunch of E/C stuff in here and I'll deal with it later.

* Fix broadphase leak

* Fix robust server sim
2021-08-23 15:01:08 +10:00
metalgearsloth
99c8ce2cc8 Attach to parent before inserting into container (#1965)
tl;dr is that throwing on content needs to be able to have IsInContainer return the correct result when checking for it in EntInsertedIntoContainer
2021-08-22 21:46:20 -07:00
Pieter-Jan Briers
f137a6e3a5 Add InheritChildMeasure bool to LayoutContainer 2021-08-22 11:32:15 +02:00
Pieter-Jan Briers
73e62414a7 Update Lidgren and NetSerializer submodules 2021-08-22 11:08:43 +02:00
Pieter-Jan Briers
ef7ef3ca9f Replace usages of NET5_0 preprocessor with NET5_0_OR_GREATER 2021-08-22 11:08:05 +02:00
Pieter-Jan Briers
da26e86f5e Merge branch 'robust-client-CEF' 2021-08-22 10:51:44 +02:00
Pieter-Jan Briers
d2a1815d7b Allow GameControllerOptions to change the startup branding. 2021-08-22 10:43:41 +02:00
Pieter-Jan Briers
4894888339 Dev window has UI tab now with UI tree. 2021-08-22 02:07:42 +02:00
Pieter-Jan Briers
3fd55733bb Add child added/removed/moved events to Control.
Intended for UI dev window
2021-08-22 02:07:42 +02:00
Pieter-Jan Briers
bebd638412 Make SplitContainer properties VV. 2021-08-22 02:07:42 +02:00
Pieter-Jan Briers
328b3cc715 Add IUserInterfaceManager.OnPostDrawUIRoot.
Intended for UI dev window highlighting.
2021-08-22 02:07:42 +02:00
Pieter-Jan Briers
b523cfddb7 Fix some layout calculation bugs.
1. Fix a copy paste typo in ApplySizeConstraints().
2. Fix negative sizes passed to MeasureCore() causing exceptions.
2021-08-22 02:07:42 +02:00
Pieter-Jan Briers
4fea277541 Make some SplitContainer stuff [ViewVariables] 2021-08-22 02:07:42 +02:00
Pieter-Jan Briers
3417f00650 Give main window UI root a good name 2021-08-22 02:07:42 +02:00
Pieter-Jan Briers
db4b190498 guidump command now shows all UI roots. 2021-08-22 02:07:38 +02:00
Pieter-Jan Briers
7d2bbc2ca7 Add an OnResized event to controls 2021-08-22 01:29:24 +02:00
Pieter-Jan Briers
553278fdb3 Fix x:Class directives in XamlUI. 2021-08-22 01:29:10 +02:00
Vera Aguilera Puerto
e5013d9e3f Add support for struct directed events, add by-ref directed/broadcast events. (#1931)
* Add support for struct directed events.
- Turn transform events into readonly structs

* TransformComponent events have readonly fields.

* Reduce boxing allocations, add DirectedRegistration readonly struct.

* Use typeof in a few places instead of GetType

* Add overloads to allow passing directed events by ref.

* Raise transform events by ref.

* Fix occluder AnchorStateChangedEvent subscription

* Fix broadphase subscriptions

* Fix bug oops

* Fix transform system raising moveevents by value

* Don't reflect the test entity systems.

* Directed EventBus uses ref everywhere, internally
Deduplicates a bunch of code. Whoo!

* Add broadcast by-ref events

* Fix by-ref events bug using Unsafe, add new tests for sorted by-ref events.

* Port broadcast events to by-ref.

* Speed, more correctness, reduce generics bloat.

* Clean up subscription code some.

* Remove bad test.

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-08-21 11:37:02 +02:00
Vera Aguilera Puerto
679f8f24c9 Cleans up EntityCoordinates, split coordinates into their own files. (#1956) 2021-08-20 15:48:43 +02:00
metalgearsloth
1a547b946c Fix anchoring parent changes
Problem was coordinates were updated first and then it was clearing the snapgrid cell.
2021-08-20 14:14:17 +10:00
metalgearsloth
5594bd7203 Revert "PVS is now aware of entity anchoring. (#1845)"
This reverts commit 820d988e0a.

# Conflicts:
#	Robust.Server/GameStates/EntityViewCulling.cs
2021-08-19 22:34:06 +10:00
metalgearsloth
8cbe48c94f Use all chunks for PVS culling
Temporary attempt at fixing the server issue with PVS.
2021-08-19 22:30:05 +10:00
metalgearsloth
b04b9fc9cd Fix timer crash 2021-08-19 22:18:31 +10:00
Acruid
820d988e0a PVS is now aware of entity anchoring. (#1845)
* PVS is now aware of entity anchoring.
Misc PVS perf improvements.

* Fix master merge

* Optimise chunks intersecting

* Ensure anchored entity chunks are always sent

* Remove silly pool by me

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2021-08-19 21:48:44 +10:00
metalgearsloth
1ad5122b5d Fix y-sorting (#1952) 2021-08-18 17:49:54 +02:00
metalgearsloth
456ac03870 Add RevoluteJoint (#1953) 2021-08-18 17:48:52 +02:00
metalgearsloth
f647d00dc4 Add short-circuit for TryDistance (#1951)
* Add short-circuit for TryDistance

90% of the uses probably have the same parent hence we can avoid the more expensive ToMap calls for these.

* InRange too
2021-08-17 08:56:16 +02:00
metalgearsloth
2fd3bbf58b Optimise TryFindGridAt (#1945) 2021-08-16 21:06:31 +02:00
metalgearsloth
5ed49d51d3 Add vertices simplifier (#1935) 2021-08-15 19:15:23 +02:00
metalgearsloth
758d5eedef Add WorldPoint to StartCollideEvent (#1943)
Some stuff may want the actual collision point.
2021-08-15 16:45:42 +02:00
metalgearsloth
96d15929e6 Optimise GetMapChunks (#1948)
Don't need to check every chunk's bounds when you can just do dictionary lookups. Should be an okay bonus for PVS and grid rendering on larger grids. Didn't make tests yet because all of the existing ones are mocked and painful to change.
2021-08-15 16:45:30 +02:00
metalgearsloth
2a062dbf53 Cleanup TimerComponent when none are running (#1949)
The ToList was showing up on a profiler.
2021-08-15 16:45:00 +02:00
metalgearsloth
6d66bc66e4 Minor grid traversal optimisation (#1950)
Avoids running duplicate events, mainly for client, but also avoids the additional GetEntity call as well.
2021-08-15 16:29:14 +02:00
Pieter-Jan Briers
5cc056100b Remove unused MsgEntity parameter packing helpers.
These were originally for when messages were passed around as an object[] args list, I believe. This isn't used so this code should just go honestly.
2021-08-12 14:35:56 +02:00
Visne
5eee22b034 Make no rotation work when eye is rotated (#1927) 2021-08-12 13:12:02 +02:00
DrSmugleaf
7f5beab259 Add the component type to the unknown component linter error message 2021-08-12 12:21:52 +02:00
metalgearsloth
46aead639b Allow VirtualControllers to be retrieved by content (#1937)
Useful in SS14 for MoverController to cache some work that SharedTileFrictionController can re-use.
2021-08-12 17:55:34 +10:00
Visne
488d060595 Fix map deletion not working because of deleted entity referencing map (#1933)
* Fix map deletion not working because of deleted entity referencing map

* Change fix

* Update Robust.Shared/GameObjects/Components/CollisionWakeComponent.cs

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2021-08-12 14:00:12 +10:00
metalgearsloth
b1bab1f38e Significantly reduce default volume range
Now it's actually unified instead of being 25 sometimes and 62.5 (the godot default) at other times.
2021-08-11 19:53:15 +10:00
metalgearsloth
feba2a23ad Fix grid collisions (#1934)
* Fix grid collisions

* Fix this silliness

* Add test for grid collisions

* Fix FlagSerializer not working with 1 << 31

Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
2021-08-10 00:04:19 +10:00
Javier Guardia Fernández
e1ff29744e Fix FlagSerializer not working with 1 << 31 (#1940) 2021-08-09 23:56:43 +10:00
Javier Guardia Fernández
f6216e63b0 Add tests for serializing flags (#1939) 2021-08-09 13:07:42 +02:00
Javier Guardia Fernández
0b3ecf2168 Add benchmarks for serializing flags (#1938)
* Add benchmarks for serializing flags

* Add type definitions
2021-08-09 13:03:24 +02:00
Vera Aguilera Puerto
9f29bf6349 Merge branch 'master' into robust-client-CEF 2021-08-09 12:02:15 +02:00
metalgearsloth
d12a238ecc Fix tests
Woops
2021-08-09 19:42:34 +10:00
Vera Aguilera Puerto
d62a64029d Merge branch 'master' into robust-client-CEF 2021-08-09 11:13:56 +02:00
metalgearsloth
ac46a7845d Fix container bounds 2021-08-09 18:46:51 +10:00
Vera Aguilera Puerto
17662aaad9 Merge branch 'master' into robust-client-CEF 2021-08-09 08:08:29 +02:00
metalgearsloth
60b526f653 Dirty physics bodies on new fixtures 2021-08-08 15:59:59 +10:00
metalgearsloth
d58e380dd9 MapManager now uses accurate bounds for grids (#1918)
* MapManager now uses accurate bounds for grids

Instead of using the old WorldBounds (which was dirty for multiple reasons) it goes through physics instead using the actual fixtures attached to the grid.

* Fix nuke

* Test time

* AABB tests

* feex

* how is this failing aaa

* feex tests

* slightly better

Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
2021-08-06 22:25:14 +10:00
Visne
96a098a0c2 Fix outline when eye is rotated (#1929) 2021-08-06 09:53:14 +02:00
Vera Aguilera Puerto
c0d4e34089 Add Friend classes to C# with the help of Analyzers and Attributes. (#1928)
* Add Friend classes to C# with the help of Analyzers and Attributes.

* Revert to netstandard2.0

* Use LINQ instead of ^1 for array

* Address review.
Oops, forgot to push.
2021-08-06 09:50:22 +02:00
ShadowCommander
e16e0f4bd0 Fix containers with entities that do not exist yet (#1892)
* Fix containers that hold entities not on client

* Delete from ExpectedEntities when entity removed

* Fix ContainerSystem not registering on the server

* Move container state to entity system
Move client code to client

* Fix removal and clean up code

* Add test

* Add more checks to test

* Remove unneeded deletion event handler

When the child is deleted, if the entity does not exist on the client,
then HandleComponentState runs. If the entity does exist, then
HandleEntityInitialized would have run. Either way HandleEntityDeleted
is not needed.

* Renamed unexpected to removedExpected
2021-08-04 19:00:14 -07:00
metalgearsloth
d31ffd2794 Update broadphase every frame on client (#1925)
There was a reason I didn't do this prior to refactor but that reason is no longer relevant soooooo
2021-08-04 00:43:32 +10:00
metalgearsloth
04d94f87fc Fix movement lerping (#1924) 2021-08-04 00:11:04 +10:00
metalgearsloth
f809375389 Cache broadphase on PhysicsMap (#1923) 2021-08-03 22:24:01 +10:00
metalgearsloth
530ea5f4e6 Remove BroadphaseMapid from physicscomp
Was made redundant in the broadphase refactor
2021-08-03 19:08:45 +10:00
Visne
590a23d540 Make it possible to set lighting per map (#1921) 2021-08-03 09:55:54 +02:00
Vera Aguilera Puerto
52351e8b11 Clyde can now render multiple viewports with different maps. (#1917)
* Clyde can now render multiple viewports with different maps.

* remove todo comment
https://tenor.com/view/emoji-disintergrating-meme-emoji-disintegrating-meme-gif-21239978

* Fix tests
2021-08-03 13:24:11 +10:00
metalgearsloth
f75a764ff3 Multi-thread physics solver (#1905) 2021-08-02 13:50:26 +02:00
metalgearsloth
d14f4f0ce3 Use structs for position constraints (#1919)
Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
2021-08-02 21:47:57 +10:00
Vera Aguilera Puerto
5367570c7f Log simple human-readable error message when PVS mail generation throws an exception. 2021-08-02 12:29:37 +02:00
Vera Aguilera Puerto
e523a733c8 TestLogHandler logs exception, too. 2021-08-02 12:29:37 +02:00
metalgearsloth
06af61106c Fix grid rendering for rotation (#1881)
* Fix grid rendering

* Actually feex

* spellcheck

* Add a test for chunk bounds

* Revert that
2021-08-02 16:41:24 +10:00
metalgearsloth
2239c30924 Fix potential contact crash 2021-08-02 16:19:40 +10:00
Vera Aguilera Puerto
f9f55b6862 Transform sets GridId before raising EntParentChangedMessage event. 2021-08-01 13:35:58 +02:00
Vera Aguilera Puerto
83a5560850 Transform EntMapIdChangedMessage is raised directed. 2021-08-01 12:20:45 +02:00
Visne
9a8f139fb9 Make ScreenToMap not default to main viewport (#1916) 2021-07-31 23:45:08 +02:00
Pieter-Jan Briers
20dae60fd4 Add NumericsHelpers add method. 2021-07-31 16:52:24 +02:00
Pieter-Jan Briers
b4098668bb Use ring buffer for IGameTiming._realFrameTimes.
List.RemoveAt(0) is inefficient.
2021-07-31 16:06:08 +02:00
mirrorcult
9397cc4a6b Fix serv warnings (#1915) 2021-07-31 15:01:18 +10:00
Pieter-Jan Briers
1601e75879 Forgot the debug code oops. 2021-07-31 04:21:37 +02:00
Pieter-Jan Briers
a7b9c87926 Save 0.4% Windows server CPU. 2021-07-31 04:20:50 +02:00
metalgearsloth
8fea42ff9a Fix GetTilesIntersecting for circles (#1912) 2021-07-30 10:01:14 +02:00
metalgearsloth
86d20a0ef1 Bump up light radius
The ones around the singularity are 32 radius for whatever reason
2021-07-30 17:22:23 +10:00
metalgearsloth
09012ea4ff Change velocity constraints to structs (#1906) 2021-07-29 22:14:01 +02:00
metalgearsloth
e68297eb93 Multi-thread physics contacts (#1897) 2021-07-29 22:13:42 +02:00
Pieter-Jan Briers
47af668cda Fix overriding unregistered-but-known config vars.
The default value would be set to 0 as a placeholder from when the config var is first tracked as unregistered, which means that when we try to override it tries to parse it as an int always.
2021-07-29 20:29:08 +02:00
metalgearsloth
c1a2e23ce2 Fix mission-critical typo 2021-07-30 00:08:17 +10:00
metalgearsloth
f208f6bfa9 Use ref var for manifold points (#1911) 2021-07-29 15:38:12 +02:00
Pieter-Jan Briers
ae526e2e10 FixedArray helpers.
May your stack allocations be fast and your GCs infrequent.
2021-07-29 15:37:43 +02:00
metalgearsloth
25549869b1 Use more values by reference in Collision (#1910)
Also a sneaky Span<ClipVertex> I forgot
2021-07-29 14:49:50 +02:00
metalgearsloth
f71e81d204 Use circle.Position for collisions. (#1909) 2021-07-29 14:49:18 +02:00
Vera Aguilera Puerto
757143be84 Merge branch 'master' into robust-client-CEF 2021-07-29 13:36:16 +02:00
metalgearsloth
e67812fdb4 Add shutdowns to VirtualControllers (#1887) 2021-07-29 13:31:34 +02:00
Vera Aguilera Puerto
aa44b1cb8a RaiseLocalEvent non-generic overload that checks for GetType() (#1907)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2021-07-29 13:30:29 +02:00
Vera Aguilera Puerto
8ec75be244 Minor ViewSubscriber cleanup, remove all mentions of PVS eye 2021-07-29 13:29:54 +02:00
Vera Aguilera Puerto
48746b7bd3 Adds ViewSubscriberComponent and ViewSubscriberSystem (#1900) 2021-07-29 13:16:25 +02:00
metalgearsloth
a9791d2033 Fix potential SetTile crash when it unanchors entities (#1903) 2021-07-29 13:15:50 +02:00
Vera Aguilera Puerto
709f1f4284 EntityLookup now uses ILookupWorldBox2Component for getting entities' world AABB. 2021-07-29 13:01:24 +02:00
metalgearsloth
907094a5c8 Minor solver optimisation for invmass 2021-07-29 18:57:35 +10:00
Pieter-Jan Briers
a35a5e1645 Add EntitySystem.Subs property to aid helper methods. 2021-07-29 01:51:51 +02:00
Pieter-Jan Briers
ad8a59a72f Revert "Adds SetButtonDisabledRecursive() method to Control" (#1902)
This reverts commit e93c0f76a9.
2021-07-27 21:44:31 +02:00
Visne
e93c0f76a9 Adds SetButtonDisabledRecursive() method to Control (#1901) 2021-07-27 20:08:24 +02:00
metalgearsloth
7bac32d18e Don't use DeferMoveEvent if the position is NaN (#1896)
The issue is that currently moving around means any non-anchored entities have their positions updated to / from NaN. As you can imagine this is a performance nightmare for anything subscribing to it, especially considering it leads to the broadphase getting desynced for physics.

Realistically we need one of the alternatives Acruid has laid out because flooding the eventbus with NaNs will kill performance.
2021-07-28 00:28:07 +10:00
Pieter-Jan Briers
b6b1d46892 Ignore "CON" in DebugConsoleLogHandler. 2021-07-27 09:38:08 +02:00
Vera Aguilera Puerto
30fcc6b729 Merge branch 'master' into robust-client-CEF 2021-07-27 09:02:29 +02:00
Vera Aguilera Puerto
6f0bc3822e InputComponent is now public. 2021-07-27 08:51:02 +02:00
Vera Aguilera Puerto
b7c8452285 Use EntitySystem dependencies in a few places that cached systems before. 2021-07-26 12:57:39 +02:00
Vera Aguilera Puerto
49e2d567cd Merge branch 'master' into robust-client-CEF 2021-07-26 12:14:11 +02:00
Paul Ritter
8c1e075c91 EntitySystem DependencyCollection & EntitySystem dependencies (#1890)
* started work

* tests

* namespace

* working

* thonk

Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
Co-authored-by: Vera Aguilera Puerto <gradientvera@outlook.com>
2021-07-26 12:10:03 +02:00
metalgearsloth
b340e40c99 Cache physics transforms internally (#1873)
* Cache physics transforms internally

3 main points to cache them for:
1. Contact updates
2. Physics Islands solving
3. GetWorldManifold (though this is primarily for ss14).

Whenever multi-threading is done it will need adjustments to make sure no race conditions on accessing the transforms dictionary

* Also cache position and angle for GetWorldAABB

* Fix boogs

* woops
2021-07-26 18:23:10 +10:00
metalgearsloth
c4b124f48d Cleanup physics contacts a bit (#1895)
Removed a lot of the listener comments as we use the eventbus instead in this house.
2021-07-25 23:40:45 +10:00
metalgearsloth
7efae8fbc1 Remove IStartCollide (#1875)
* Remove IStartCollide

Needs several content PRs to be merged as well.

* Feex
2021-07-25 23:06:34 +10:00
Pieter-Jan Briers
7feeeb2f6f OOps 2021-07-25 14:50:14 +02:00
mirrorcult
f90462cf82 Improve RSI error messages (#1894) 2021-07-25 12:06:36 +02:00
metalgearsloth
b19ae9e69e Add AABB VVs for debugging 2021-07-25 19:04:25 +10:00
Vera Aguilera Puerto
9bf69db0ef Merge branch 'master' into robust-client-CEF 2021-07-24 10:06:54 +02:00
metalgearsloth
2132d6cbae Comment out the contactmanager crash safeguard for now 2021-07-24 13:50:27 +10:00
Ygg01
d2d6f9d08e Update Linguini to latest to fix escaping text literals (#1880) 2021-07-23 17:43:14 +02:00
metalgearsloth
4b58fcbff2 Fix anchoring on moved grid (#1885) 2021-07-23 10:16:19 +02:00
metalgearsloth
f83f6a8cd6 Remove manifolds from collision events
Nothing uses em anymore.
2021-07-23 13:06:24 +10:00
metalgearsloth
dfd7711506 Stop EndCollision from throwing exceptions
Won't fix the underlying problem but will log it and handle it more gracefully at least until I can reproduce it.
2021-07-23 13:03:54 +10:00
Pieter-Jan Briers
78f9d92c07 Add exception tolerance to DispatchEntityNetworkMessage. 2021-07-22 23:27:39 +02:00
Paul
3a86c827ea made xamlil errors show up in msbuild 2021-07-22 20:01:15 +02:00
metalgearsloth
325f25c547 Fix occluders for moved grid (#1878)
* Refactor occluders

* Copy pasting

* Reduce bounds

* Clear system updates on shutdown
2021-07-22 13:07:45 +10:00
Pieter-Jan Briers
be57b5d20b Add AnyCommandExecuted callback to IConsoleHost.
Intended use case here is for content to listen to ConCmds for AFK detection.
2021-07-21 21:32:32 +02:00
Pieter-Jan Briers
7124d86f94 Allow localization to pass through TimeSpan values. 2021-07-21 18:58:43 +02:00
Pieter-Jan Briers
229380a71d Add TimeSpan read/write helpers to NetMessageExt 2021-07-21 16:25:15 +02:00
metalgearsloth
e9eb536df5 Don't get fixture pairs if they're deleted 2021-07-21 23:08:10 +10:00
metalgearsloth
22297ef6d8 Out of the way System.Numerics 2021-07-21 21:14:41 +10:00
metalgearsloth
7f2e433087 Broadphase refactor (#1848)
* Broadphase refactor

* Stuff

* Working

* TODO

* Changes

* Which fucking madman came up with this shit

* Known gud state

* Kinda shitcodey but it works so fuck it doin it live

* Shuttle jankiness

* Refactor entitylookups to be 30% more based

* Refactor gucci

* Done?

* Fix most tests

* nothing suss

* Vera single-handedly saving shuttles

* fex

* Fix renderingtreecomp for relativity

* Fix IEntityLookup

* Fixes

* Testing jank please revert some of it before merging

* Fix the remaining bugs

* Fix crash

* Fix grid physics initialization

* Shuttle collisions working

* Fixes

* Fixes

* Velocity on transfers

* Velocity on parent change

* Fixes

* world angular velocity too

* Slightly faster map velocities

* showbb revert

* Sketch grids kinda workin

* Fix PVS contact crash

* Cleanup gridfixture updates 0.1%

* Grid fixtures

* AAAAAAAAAAAAAAA

* a

* termp

* Fixes

* Test reversion reversion?

* Tests

* Fix Equals

* Slight box2i cleanup

* Better initializer

* Fix merge issues

* Shuttles go BRRT

* Remove MoveProxy from DynamicTreeBroadphase

* Optimise a shit tonne

* Approx

* fix showbb

* clean

* Slightly more optimised

* My almonds are activating

* Contact transform caching

* Avoid duplicates

* Typo

* Logger

* Jitter 1% better

* Okay shit maybe that's not it.

* Contact fixes

* Move check thingy up front

* Revert some jank caching

* Contact filtering

* Fix master merge

* Test fixes

* Re-fix MapLoaderTest

* Use proxies instead

* uhh wtf?

* Woops

* Fix collisions

* Fix grid fixture generation

* Fix deserializing

* Tile window fix

* Cleanup broadphase

* Bit more cleanup

Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
2021-07-21 21:12:58 +10:00
Pieter-Jan Briers
18c32a0258 Don't ServerSendToAll to channels without completed handshake. 2021-07-21 03:06:32 +02:00
Pieter-Jan Briers
72314a102d EventBus improvements
Can do ordered session event subscription.

Can subscribe to "All" in one call, to reduce shared boilerplate.
2021-07-21 00:56:42 +02:00
Pieter-Jan Briers
719ea26a31 IConfigurationManager.UnsubValueChanged. 2021-07-20 17:26:52 +02:00
metalgearsloth
5cb8fe1897 End IEndCollide (#1874) 2021-07-20 17:01:30 +02:00
metalgearsloth
f35a52fc24 Change KinematicControllerCollision to use WorldNormal instead (#1872)
Content cares about the WorldNormal and not the LocalNormal so this fixes pushing bugs.
2021-07-19 10:17:39 +02:00
metalgearsloth
6bdb0cef47 Fix IMapGrid WorldToLocal + LocalToWorld (#1871) 2021-07-19 10:17:15 +02:00
Pieter-Jan Briers
fe3c9fe28f Fix late nwVar warning on connect. 2021-07-19 10:16:24 +02:00
Pieter-Jan Briers
6085671f22 Fix memory leak with physics islands and contact solvers.
They were doing config OnValueChanged for every instance and, as such, never getting released.
2021-07-19 10:06:20 +02:00
Visne
a2398da324 Replace most VBox/HBoxContainers with BoxContainers (#1867) 2021-07-18 18:42:08 +02:00
Pieter-Jan Briers
b27304cc58 Add System.Index and System.Range to sandbox whitelist. 2021-07-17 23:51:57 +02:00
Swept
3bf851a6cf FPS Counter no longer shows a decimal 2021-07-17 16:46:09 +00:00
Pieter-Jan Briers
cef92efd0f NetManager now enforces that only specific messages can be sent before serializer handshake completes. 2021-07-17 14:37:40 +02:00
Pieter-Jan Briers
c5961a5ab1 Fix possible race condition in net var handling and improve logging. 2021-07-17 14:37:35 +02:00
Pieter-Jan Briers
8ddd92993d Don't do initial net var stuff on server. 2021-07-17 02:03:37 +02:00
Pieter-Jan Briers
da253a5f34 Log user ID correctly on connection approval 2021-07-17 02:02:59 +02:00
Pieter-Jan Briers
ca9400a1ff Don't log encryption secrets on auth handshake. 2021-07-17 01:24:39 +02:00
Pieter-Jan Briers
f232195ceb Fix audio log warning not using interpolated string correctly. 2021-07-17 01:17:23 +02:00
Pieter-Jan Briers
b54a803519 Add ToString to NetChannel.
Some things already try to log like this so let's just go with it.
2021-07-17 01:16:47 +02:00
Vera Aguilera Puerto
a0d3d2108f Use ResourcePath instead of Path.Join 2021-07-16 21:48:19 +02:00
Vera Aguilera Puerto
977e4a017b Fixes tile window hardcoding tile sprites. 2021-07-16 08:21:58 +02:00
Swept
2d8b159016 Updates the dumb shit stupid path for tile window 2021-07-16 05:38:19 +00:00
Visne
9caa0dde4b Remove unused IEntityManager parameter from EntityCoordinates.FromMap() (#1866) 2021-07-15 18:51:11 +02:00
Pieter-Jan Briers
7a5a8c5eb1 Remove unused Process.WaitForExitAsync helper.
.NET 5 has its own implementation so we use that now.
2021-07-15 10:07:52 +02:00
Swept
95ba58f0a4 Fixes SpriteComponent error reporting 2021-07-14 22:41:51 +00:00
metalgearsloth
f780f04784 Deprecate PhysShapeGrid (#1862)
* Grid fixtures

* termp

* Fixes

* Test reversion reversion?

* Tests

* Fix Equals

* Slight box2i cleanup

* Better initializer

* Also add static grid assert
2021-07-14 18:47:17 +10:00
Pieter-Jan Briers
695b4ce8f2 Merge branch 'master' into robust-client-CEF 2021-07-13 17:22:20 +02:00
Pieter-Jan Briers
85782bda92 Make Split- and BoxContainer orientation adjustable, obsolete subtypes. 2021-07-13 17:21:21 +02:00
metalgearsloth
14a01df5b1 Add physics stacking tests (#1865)
Ported from content; only reason for PR is to make sure remote tests are also gucci.
2021-07-13 18:43:09 +10:00
metalgearsloth
644da60bfc ContactCount VV (#1864) 2021-07-13 14:11:27 +10:00
Pieter-Jan Briers
8c83999ad2 Make window creation synchronous.
The async code path isn't really async and if we ever make it so (by using a non shit rendering API) I'll just make it implicitly asynchronous.
2021-07-13 03:39:38 +02:00
Pieter-Jan Briers
24b9fc9eec Add ImageSharp to script console assemblies. 2021-07-12 17:26:54 +02:00
metalgearsloth
ba40185179 Make entitylookup grid-relative (#1849)
* Refactor entitylookups to be 30% more based

* Refactor gucci

* Done?

* Fix most tests

* nothing suss

* Vera single-handedly saving shuttles

* fex

* Copy lookup from shuttles

* sys

* comp recursion
2021-07-12 13:39:02 +02:00
Vera Aguilera Puerto
8b013cb424 Fix engine integration tests not generating Net IDs. 2021-07-12 10:42:38 +02:00
Vera Aguilera Puerto
d7ecc0883f Merge branch 'master' into robust-client-CEF 2021-07-08 10:00:46 +02:00
Vera Aguilera Puerto
44b17edff6 Fix failing unit test. 2021-07-06 10:38:48 +02:00
Pieter-Jan Briers
6366c2383a Request handling API. 2021-07-06 02:07:17 +02:00
Pieter-Jan Briers
ddbbe22bda Make RobustCefApp internal. 2021-07-05 23:41:01 +02:00
Pieter-Jan Briers
6880af0d8b Lifetime management for BrowserControl
Correctly shuts down all resources when removed fromt ree.
2021-07-05 23:35:16 +02:00
Vera Aguilera Puerto
6f258b1822 Fix multi process mode on Linux. 2021-07-05 23:10:19 +02:00
Pieter-Jan Briers
2cf3ed8a70 External window lifetime management. 2021-07-05 21:58:43 +02:00
Pieter-Jan Briers
c6f10a1321 Don't make a RobustCefApp in CEF sub processes. 2021-07-05 20:26:16 +02:00
Vera Aguilera Puerto
41ab9b106f Add RobustCefApp, CefManager no longer inherits CefApp. 2021-07-05 20:22:00 +02:00
Pieter-Jan Briers
0d97569576 Implement mouse modifiers. 2021-07-05 20:18:54 +02:00
Pieter-Jan Briers
0a21d402b2 Apparently I didn't disable disable GPU properly. 2021-07-05 20:16:50 +02:00
Pieter-Jan Briers
c6827fe8ae Merge remote-tracking branch 'origin/robust-client-CEF' into robust-client-CEF 2021-07-05 18:23:05 +02:00
Pieter-Jan Briers
0c94956be1 Various degrees of work on input stuff. 2021-07-05 18:21:47 +02:00
Pieter-Jan Briers
1e7a193d5e Raise scroll speed 2021-07-05 11:44:42 +02:00
Vera Aguilera Puerto
eb4ea8aa63 Adds a few APIs to IBrowserControl and implements them where needed 2021-07-05 11:23:44 +02:00
Pieter-Jan Briers
af1a3d965d Actually shut down loaded modules on client 2021-07-05 10:27:21 +02:00
Pieter-Jan Briers
26fed40ad4 Remove fixed TODO comment 2021-07-05 10:24:10 +02:00
Pieter-Jan Briers
c6c6309eff Disable disable GPU 2021-07-05 10:05:10 +02:00
Pieter-Jan Briers
7188eeb0c8 Add external window creation API 2021-07-05 09:37:04 +02:00
Pieter-Jan Briers
d74b22a4e8 Engine API for fetching win32 HWND. 2021-07-05 09:05:59 +02:00
Pieter-Jan Briers
75de717b4e Enable CEF multi-process mode. 2021-07-05 08:00:16 +02:00
Pieter-Jan Briers
a00fc75b61 BrowserControl less inefficient rendering 2021-07-05 07:59:56 +02:00
Pieter-Jan Briers
dcbb6608df Improve clyde SetSubImage.
1. better perf.
2. code cleaned up
3. span-based API.
2021-07-05 07:55:52 +02:00
Pieter-Jan Briers
e4d0e50000 Add Robust.Client.CEF to InternalsVisibleTo 2021-07-05 07:46:59 +02:00
Pieter-Jan Briers
e5a2ab284b Merge branch 'master' into robust-client-CEF 2021-07-04 19:56:41 +02:00
Vera Aguilera Puerto
3ac1506f17 Add correct license header to BitmapBuffer 2021-07-04 18:22:04 +02:00
Vera Aguilera Puerto
c85bb81606 Add initial CEF integration to engine. 2021-07-02 23:31:37 +02:00
353 changed files with 12374 additions and 4726 deletions

3
.gitmodules vendored
View File

@@ -16,3 +16,6 @@
[submodule "Linguini"]
path = Linguini
url = https://github.com/space-wizards/Linguini
[submodule "cefglue"]
path = cefglue
url = https://gitlab.com/xiliumhq/chromiumembedded/cefglue/

View File

@@ -129,5 +129,7 @@ namespace OpenToolkit.GraphicsLibraryFramework
/// for controlling sRGB rendering and a created OpenGL ES context will always have sRGB rendering enabled.
/// </summary>
SrgbCapable = 0x0002100E,
ScaleToMonitor = 0x0002200C,
}
}

View File

@@ -5551,5 +5551,10 @@ namespace OpenToolkit.GraphicsLibraryFramework
{
return glfwGetX11Window(window);
}
public static unsafe IntPtr GetWin32Window(Window* window)
{
return glfwGetWin32Window(window);
}
}
}

View File

@@ -406,5 +406,8 @@ namespace OpenToolkit.GraphicsLibraryFramework
[DllImport(LibraryName)]
public static extern uint glfwGetX11Window(Window* window);
[DllImport(LibraryName)]
public static extern IntPtr glfwGetWin32Window(Window* window);
}
}

View File

@@ -0,0 +1,107 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Robust.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class FriendAnalyzer : DiagnosticAnalyzer
{
const string FriendAttribute = "Robust.Shared.Analyzers.FriendAttribute";
public const string DiagnosticId = "RA0002";
private const string Title = "Tried to access friend-only member";
private const string MessageFormat = "Tried to access member \"{0}\" in class \"{1}\" which can only be accessed by friend classes";
private const string Description = "Make sure to specify the accessing class in the friends attribute.";
private const string Category = "Usage";
[SuppressMessage("ReSharper", "RS2008")]
private static readonly DiagnosticDescriptor Rule = new (DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, true, Description);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(CheckFriendship, SyntaxKind.SimpleMemberAccessExpression);
}
private void CheckFriendship(SyntaxNodeAnalysisContext context)
{
if (context.Node is not MemberAccessExpressionSyntax memberAccess)
return;
// We only do something if our parent is one of a few types.
switch (context.Node.Parent)
{
// If we're being assigned...
case AssignmentExpressionSyntax assignParent:
{
if (assignParent.Left != memberAccess)
return;
break;
}
// If we're being invoked...
case InvocationExpressionSyntax:
break;
// Otherwise, do nothing.
default:
return;
}
// Get the friend attribute
var friendAttr = context.Compilation.GetTypeByMetadataName(FriendAttribute);
// Get the type that is containing this expression, or, the class where this is happening.
if (context.ContainingSymbol?.ContainingType is not { } containingType)
return;
// We check all of our children and get only the identifiers.
foreach (var identifier in memberAccess.ChildNodes().Select(node => node as IdentifierNameSyntax))
{
if (identifier == null) continue;
// Get the type info of the identifier, so we can check the attributes...
if (context.SemanticModel.GetTypeInfo(identifier).ConvertedType is not { } type)
continue;
// Same-type access is always fine.
if (SymbolEqualityComparer.Default.Equals(type, containingType))
continue;
// Finally, get all attributes of the type, to check if we have any friend classes.
foreach (var attribute in type.GetAttributes())
{
// If the attribute isn't the friend attribute, continue.
if (!SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, friendAttr))
continue;
// Check all types allowed in the friend attribute. (We assume there's only one constructor arg.)
foreach (var constant in attribute.ConstructorArguments[0].Values)
{
// Check if the value is a type...
if (constant.Value is not INamedTypeSymbol t)
continue;
// If we find that the containing class is specified in the attribute, return! All is good.
if (SymbolEqualityComparer.Default.Equals(containingType, t))
return;
}
// Not in a friend class! Report an error.
context.ReportDiagnostic(
Diagnostic.Create(Rule, context.Node.GetLocation(),
$"{context.Node.ToString().Split('.').LastOrDefault()}", $"{type.Name}"));
}
}
}
}
}

View File

@@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>9</LangVersion>
</PropertyGroup>
<ItemGroup>

View File

@@ -0,0 +1,28 @@
using BenchmarkDotNet.Attributes;
namespace Robust.Benchmarks.NumericsHelpers
{
public class AddBenchmark
{
[Params(32, 128)]
public int N { get; set; }
private float[] _inputA = default!;
private float[] _inputB = default!;
private float[] _output = default!;
[GlobalSetup]
public void Setup()
{
_inputA = new float[N];
_inputB = new float[N];
_output = new float[N];
}
[Benchmark]
public void Bench()
{
Shared.Maths.NumericsHelpers.Add(_inputA, _inputB, _output);
}
}
}

View File

@@ -6,7 +6,7 @@ namespace Robust.Benchmarks
{
public static void Main(string[] args)
{
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run();
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
}
}
}

View File

@@ -6,6 +6,7 @@ using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
@@ -35,6 +36,10 @@ namespace Robust.Benchmarks.Serialization.Copy
private SeedDataDefinition Seed { get; }
private BenchmarkFlagsEnum FlagZero = BenchmarkFlagsEnum.Zero;
private BenchmarkFlagsEnum FlagThirtyOne = BenchmarkFlagsEnum.ThirtyOne;
[Benchmark]
public string? CreateCopyString()
{
@@ -111,5 +116,25 @@ namespace Robust.Benchmarks.Serialization.Copy
return copy;
}
[Benchmark]
[BenchmarkCategory("flag")]
public object? CreateCopyFlagZero()
{
return SerializationManager.CopyWithTypeSerializer(
typeof(FlagSerializer<BenchmarkFlags>),
(int) FlagZero,
(int) FlagZero);
}
[Benchmark]
[BenchmarkCategory("flag")]
public object? CreateCopyFlagThirtyOne()
{
return SerializationManager.CopyWithTypeSerializer(
typeof(FlagSerializer<BenchmarkFlags>),
(int) FlagThirtyOne,
(int) FlagThirtyOne);
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
using Robust.Shared.Serialization;
namespace Robust.Benchmarks.Serialization.Definitions
{
public class BenchmarkFlags
{
public const int Zero = 1 << 0;
public const int ThirtyOne = 1 << 31;
}
[Flags]
[FlagsFor(typeof(BenchmarkFlags))]
public enum BenchmarkFlagsEnum
{
Zero = BenchmarkFlags.Zero,
ThirtyOne = BenchmarkFlags.ThirtyOne
}
}

View File

@@ -1,10 +1,12 @@
using System.IO;
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using YamlDotNet.RepresentationModel;
namespace Robust.Benchmarks.Serialization.Read
@@ -32,6 +34,10 @@ namespace Robust.Benchmarks.Serialization.Read
private MappingDataNode SeedNode { get; }
private ValueDataNode FlagZero { get; } = new("Zero");
private ValueDataNode FlagThirtyOne { get; } = new("ThirtyOne");
[Benchmark]
public string? ReadString()
{
@@ -55,5 +61,25 @@ namespace Robust.Benchmarks.Serialization.Read
{
return SerializationManager.ReadValue<SeedDataDefinition>(SeedNode);
}
[Benchmark]
[BenchmarkCategory("flag")]
public DeserializationResult ReadFlagZero()
{
return SerializationManager.ReadWithTypeSerializer(
typeof(int),
typeof(FlagSerializer<BenchmarkFlags>),
FlagZero);
}
[Benchmark]
[BenchmarkCategory("flag")]
public DeserializationResult ReadThirtyOne()
{
return SerializationManager.ReadWithTypeSerializer(
typeof(int),
typeof(FlagSerializer<BenchmarkFlags>),
FlagThirtyOne);
}
}
}

View File

@@ -7,6 +7,7 @@ using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using YamlDotNet.RepresentationModel;
namespace Robust.Benchmarks.Serialization.Write
@@ -35,6 +36,10 @@ namespace Robust.Benchmarks.Serialization.Write
private SeedDataDefinition Seed { get; }
private BenchmarkFlagsEnum FlagZero = BenchmarkFlagsEnum.Zero;
private BenchmarkFlagsEnum FlagThirtyOne = BenchmarkFlagsEnum.ThirtyOne;
[Benchmark]
public DataNode WriteString()
{
@@ -94,5 +99,25 @@ namespace Robust.Benchmarks.Serialization.Write
return mapping;
}
[Benchmark]
[BenchmarkCategory("flag")]
public DataNode WriteFlagZero()
{
return SerializationManager.WriteWithTypeSerializer(
typeof(int),
typeof(FlagSerializer<BenchmarkFlags>),
FlagZero);
}
[Benchmark]
[BenchmarkCategory("flag")]
public DataNode WriteThirtyOne()
{
return SerializationManager.WriteWithTypeSerializer(
typeof(int),
typeof(FlagSerializer<BenchmarkFlags>),
FlagThirtyOne);
}
}
}

View File

@@ -0,0 +1,32 @@
using Xilium.CefGlue;
namespace Robust.Client.CEF
{
public sealed class BeforeBrowseContext
{
internal readonly CefRequest CefRequest;
public string Url => CefRequest.Url;
public string Method => CefRequest.Method;
public bool IsRedirect { get; }
public bool UserGesture { get; }
public bool IsCancelled { get; private set; }
internal BeforeBrowseContext(
bool isRedirect,
bool userGesture,
CefRequest cefRequest)
{
CefRequest = cefRequest;
IsRedirect = isRedirect;
UserGesture = userGesture;
}
public void DoCancel()
{
IsCancelled = true;
}
}
}

View File

@@ -0,0 +1,593 @@
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using SixLabors.ImageSharp.PixelFormats;
using Xilium.CefGlue;
using static Robust.Client.CEF.CefKeyCodes;
using static Robust.Client.CEF.CefKeyCodes.ChromiumKeyboardCode;
using static Robust.Client.Input.Keyboard;
namespace Robust.Client.CEF
{
// Funny browser control to integrate in UI.
public class BrowserControl : Control, IBrowserControl, IRawInputControl
{
private const int ScrollSpeed = 50;
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IInputManager _inputMgr = default!;
[Dependency] private readonly CefManager _cef = default!;
private RobustRequestHandler _requestHandler = new RobustRequestHandler(Logger.GetSawmill("root"));
private LiveData? _data;
private string _startUrl = "about:blank";
[ViewVariables(VVAccess.ReadWrite)]
public string Url
{
get => _data == null ? _startUrl : _data.Browser.GetMainFrame().Url;
set
{
if (_data == null)
_startUrl = value;
else
_data.Browser.GetMainFrame().LoadUrl(value);
}
}
[ViewVariables] public bool IsLoading => _data?.Browser.IsLoading ?? false;
private readonly Dictionary<Key, ChromiumKeyboardCode> _keyMap = new()
{
[Key.A] = VKEY_A,
[Key.B] = VKEY_B,
[Key.C] = VKEY_C,
[Key.D] = VKEY_D,
[Key.E] = VKEY_E,
[Key.F] = VKEY_F,
[Key.G] = VKEY_G,
[Key.H] = VKEY_H,
[Key.I] = VKEY_I,
[Key.J] = VKEY_J,
[Key.K] = VKEY_K,
[Key.L] = VKEY_L,
[Key.M] = VKEY_M,
[Key.N] = VKEY_N,
[Key.O] = VKEY_O,
[Key.P] = VKEY_P,
[Key.Q] = VKEY_Q,
[Key.R] = VKEY_R,
[Key.S] = VKEY_S,
[Key.T] = VKEY_T,
[Key.U] = VKEY_U,
[Key.V] = VKEY_V,
[Key.W] = VKEY_W,
[Key.X] = VKEY_X,
[Key.Y] = VKEY_Y,
[Key.Z] = VKEY_Z,
[Key.Num0] = VKEY_0,
[Key.Num1] = VKEY_1,
[Key.Num2] = VKEY_2,
[Key.Num3] = VKEY_3,
[Key.Num4] = VKEY_4,
[Key.Num5] = VKEY_5,
[Key.Num6] = VKEY_6,
[Key.Num7] = VKEY_7,
[Key.Num8] = VKEY_8,
[Key.Num9] = VKEY_9,
[Key.NumpadNum0] = VKEY_NUMPAD0,
[Key.NumpadNum1] = VKEY_NUMPAD1,
[Key.NumpadNum2] = VKEY_NUMPAD2,
[Key.NumpadNum3] = VKEY_NUMPAD3,
[Key.NumpadNum4] = VKEY_NUMPAD4,
[Key.NumpadNum5] = VKEY_NUMPAD5,
[Key.NumpadNum6] = VKEY_NUMPAD6,
[Key.NumpadNum7] = VKEY_NUMPAD7,
[Key.NumpadNum8] = VKEY_NUMPAD8,
[Key.NumpadNum9] = VKEY_NUMPAD9,
[Key.Escape] = VKEY_ESCAPE,
[Key.Control] = VKEY_CONTROL,
[Key.Shift] = VKEY_SHIFT,
[Key.Alt] = VKEY_MENU,
[Key.LSystem] = VKEY_LWIN,
[Key.RSystem] = VKEY_RWIN,
[Key.LBracket] = VKEY_OEM_4,
[Key.RBracket] = VKEY_OEM_6,
[Key.SemiColon] = VKEY_OEM_1,
[Key.Comma] = VKEY_OEM_COMMA,
[Key.Period] = VKEY_OEM_PERIOD,
[Key.Apostrophe] = VKEY_OEM_7,
[Key.Slash] = VKEY_OEM_2,
[Key.BackSlash] = VKEY_OEM_5,
[Key.Tilde] = VKEY_OEM_3,
[Key.Equal] = VKEY_OEM_PLUS,
[Key.Space] = VKEY_SPACE,
[Key.Return] = VKEY_RETURN,
[Key.BackSpace] = VKEY_BACK,
[Key.Tab] = VKEY_TAB,
[Key.PageUp] = VKEY_PRIOR,
[Key.PageDown] = VKEY_NEXT,
[Key.End] = VKEY_END,
[Key.Home] = VKEY_HOME,
[Key.Insert] = VKEY_INSERT,
[Key.Delete] = VKEY_DELETE,
[Key.Minus] = VKEY_OEM_MINUS,
[Key.NumpadAdd] = VKEY_ADD,
[Key.NumpadSubtract] = VKEY_SUBTRACT,
[Key.NumpadDivide] = VKEY_DIVIDE,
[Key.NumpadMultiply] = VKEY_MULTIPLY,
[Key.NumpadDecimal] = VKEY_DECIMAL,
[Key.Left] = VKEY_LEFT,
[Key.Right] = VKEY_RIGHT,
[Key.Up] = VKEY_UP,
[Key.Down] = VKEY_DOWN,
[Key.F1] = VKEY_F1,
[Key.F2] = VKEY_F2,
[Key.F3] = VKEY_F3,
[Key.F4] = VKEY_F4,
[Key.F5] = VKEY_F5,
[Key.F6] = VKEY_F6,
[Key.F7] = VKEY_F7,
[Key.F8] = VKEY_F8,
[Key.F9] = VKEY_F9,
[Key.F10] = VKEY_F10,
[Key.F11] = VKEY_F11,
[Key.F12] = VKEY_F12,
[Key.F13] = VKEY_F13,
[Key.F14] = VKEY_F14,
[Key.F15] = VKEY_F15,
[Key.Pause] = VKEY_PAUSE,
};
public BrowserControl()
{
CanKeyboardFocus = true;
KeyboardFocusOnClick = true;
MouseFilter = MouseFilterMode.Stop;
IoCManager.InjectDependencies(this);
}
protected override void EnteredTree()
{
base.EnteredTree();
_cef.CheckInitialized();
DebugTools.AssertNull(_data);
// A funny render handler that will allow us to render to the control.
var renderer = new ControlRenderHandler(this);
// A funny web cef client. This can actually be shared by multiple browsers, but I'm not sure how the
// rendering would work in that case? TODO CEF: Investigate a way to share the web client?
var client = new RobustCefClient(renderer, _requestHandler, new RobustLoadHandler());
var info = CefWindowInfo.Create();
// FUNFACT: If you DO NOT set these below and set info.Width/info.Height instead, you get an external window
// Good to know, huh? Setup is the same, except you can pass a dummy render handler to the CEF client.
info.SetAsWindowless(IntPtr.Zero, false); // TODO CEF: Pass parent handle?
info.WindowlessRenderingEnabled = true;
var settings = new CefBrowserSettings()
{
WindowlessFrameRate = 60
};
// Create the web browser! And by default, we go to about:blank.
var browser = CefBrowserHost.CreateBrowserSync(info, client, settings, _startUrl);
var texture = _clyde.CreateBlankTexture<Bgra32>(Vector2i.One);
_data = new LiveData(texture, client, browser, renderer);
}
protected override void ExitedTree()
{
base.ExitedTree();
DebugTools.AssertNotNull(_data);
_data!.Texture.Dispose();
_data.Browser.GetHost().CloseBrowser(true);
_data = null;
}
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
{
base.MouseMove(args);
if (_data == null)
return;
// Logger.Debug();
var modifiers = CalcMouseModifiers();
var mouseEvent = new CefMouseEvent(
(int) args.RelativePosition.X, (int) args.RelativePosition.Y,
modifiers);
_data.Browser.GetHost().SendMouseMoveEvent(mouseEvent, false);
}
protected internal override void MouseExited()
{
base.MouseExited();
if (_data == null)
return;
var modifiers = CalcMouseModifiers();
_data.Browser.GetHost().SendMouseMoveEvent(new CefMouseEvent(0, 0, modifiers), true);
}
protected internal override void MouseWheel(GUIMouseWheelEventArgs args)
{
base.MouseWheel(args);
if (_data == null)
return;
var modifiers = CalcMouseModifiers();
var mouseEvent = new CefMouseEvent(
(int) args.RelativePosition.X, (int) args.RelativePosition.Y,
modifiers);
_data.Browser.GetHost().SendMouseWheelEvent(
mouseEvent,
(int) args.Delta.X * ScrollSpeed,
(int) args.Delta.Y * ScrollSpeed);
}
bool IRawInputControl.RawKeyEvent(in GuiRawKeyEvent guiRawEvent)
{
if (_data == null)
return false;
var host = _data.Browser.GetHost();
if (guiRawEvent.Key is Key.MouseLeft or Key.MouseMiddle or Key.MouseRight)
{
var key = guiRawEvent.Key switch
{
Key.MouseLeft => CefMouseButtonType.Left,
Key.MouseMiddle => CefMouseButtonType.Middle,
Key.MouseRight => CefMouseButtonType.Right,
_ => default // not possible
};
var mouseEvent = new CefMouseEvent(
guiRawEvent.MouseRelative.X, guiRawEvent.MouseRelative.Y,
CefEventFlags.None);
// Logger.Debug($"MOUSE: {guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {key}");
// TODO: double click support?
host.SendMouseClickEvent(mouseEvent, key, guiRawEvent.Action == RawKeyAction.Up, 1);
}
else
{
// TODO: Handle left/right modifier keys??
if (!_keyMap.TryGetValue(guiRawEvent.Key, out var vkKey))
vkKey = default;
// Logger.Debug($"{guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {vkKey}");
var lParam = 0;
lParam |= (guiRawEvent.ScanCode & 0xFF) << 16;
if (guiRawEvent.Action != RawKeyAction.Down)
lParam |= 1 << 30;
if (guiRawEvent.Action == RawKeyAction.Up)
lParam |= 1 << 31;
var modifiers = CalcModifiers(guiRawEvent.Key);
host.SendKeyEvent(new CefKeyEvent
{
// Repeats are sent as key downs, I guess?
EventType = guiRawEvent.Action == RawKeyAction.Up
? CefKeyEventType.KeyUp
: CefKeyEventType.RawKeyDown,
NativeKeyCode = lParam,
// NativeKeyCode = guiRawEvent.ScanCode,
WindowsKeyCode = (int) vkKey,
IsSystemKey = false, // TODO
Modifiers = modifiers
});
if (guiRawEvent.Action != RawKeyAction.Up && guiRawEvent.Key == Key.Return)
{
host.SendKeyEvent(new CefKeyEvent
{
EventType = CefKeyEventType.Char,
WindowsKeyCode = '\r',
NativeKeyCode = lParam,
Modifiers = modifiers
});
}
}
return true;
}
private CefEventFlags CalcModifiers(Key key)
{
CefEventFlags modifiers = default;
if (_inputMgr.IsKeyDown(Key.Control))
modifiers |= CefEventFlags.ControlDown;
if (_inputMgr.IsKeyDown(Key.Alt))
modifiers |= CefEventFlags.AltDown;
if (_inputMgr.IsKeyDown(Key.Shift))
modifiers |= CefEventFlags.ShiftDown;
if (_inputMgr.IsKeyDown(Key.Shift))
modifiers |= CefEventFlags.ShiftDown;
return modifiers;
}
private CefEventFlags CalcMouseModifiers()
{
CefEventFlags modifiers = default;
if (_inputMgr.IsKeyDown(Key.Control))
modifiers |= CefEventFlags.ControlDown;
if (_inputMgr.IsKeyDown(Key.Alt))
modifiers |= CefEventFlags.AltDown;
if (_inputMgr.IsKeyDown(Key.Shift))
modifiers |= CefEventFlags.ShiftDown;
if (_inputMgr.IsKeyDown(Key.Shift))
modifiers |= CefEventFlags.ShiftDown;
if (_inputMgr.IsKeyDown(Key.MouseLeft))
modifiers |= CefEventFlags.LeftMouseButton;
if (_inputMgr.IsKeyDown(Key.MouseMiddle))
modifiers |= CefEventFlags.MiddleMouseButton;
if (_inputMgr.IsKeyDown(Key.MouseRight))
modifiers |= CefEventFlags.RightMouseButton;
return modifiers;
}
protected internal override void TextEntered(GUITextEventArgs args)
{
base.TextEntered(args);
if (_data == null)
return;
var host = _data.Browser.GetHost();
Span<char> buf = stackalloc char[2];
var written = args.AsRune.EncodeToUtf16(buf);
for (var i = 0; i < written; i++)
{
host.SendKeyEvent(new CefKeyEvent
{
EventType = CefKeyEventType.Char,
WindowsKeyCode = buf[i],
Character = buf[i],
UnmodifiedCharacter = buf[i]
});
}
}
protected override void Resized()
{
base.Resized();
if (_data == null)
return;
_data.Browser.GetHost().NotifyMoveOrResizeStarted();
_data.Browser.GetHost().WasResized();
_data.Texture.Dispose();
_data.Texture = _clyde.CreateBlankTexture<Bgra32>((PixelWidth, PixelHeight));
}
protected internal override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
if (_data == null)
return;
var bufImg = _data.Renderer.Buffer.Buffer;
_data.Texture.SetSubImage(
Vector2i.Zero,
bufImg,
new UIBox2i(
0, 0,
Math.Min(PixelWidth, bufImg.Width),
Math.Min(PixelHeight, bufImg.Height)));
handle.DrawTexture(_data.Texture, Vector2.Zero);
}
public void StopLoad()
{
if (_data == null)
throw new InvalidOperationException();
_data.Browser.StopLoad();
}
public void Reload()
{
if (_data == null)
throw new InvalidOperationException();
_data.Browser.Reload();
}
public bool GoBack()
{
if (_data == null)
throw new InvalidOperationException();
if (!_data.Browser.CanGoBack)
return false;
_data.Browser.GoBack();
return true;
}
public bool GoForward()
{
if (_data == null)
throw new InvalidOperationException();
if (!_data.Browser.CanGoForward)
return false;
_data.Browser.GoForward();
return true;
}
public void ExecuteJavaScript(string code)
{
if (_data == null)
throw new InvalidOperationException();
// TODO: this should not run until the browser is done loading seriously does this even work?
_data.Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1);
}
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
{
_requestHandler.AddResourceRequestHandler(handler);
}
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
{
_requestHandler.RemoveResourceRequestHandler(handler);
}
public void AddBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
{
_requestHandler.AddBeforeBrowseHandler(handler);
}
public void RemoveBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
{
_requestHandler.RemoveBeforeBrowseHandler(handler);
}
private sealed class LiveData
{
public OwnedTexture Texture;
public readonly RobustCefClient Client;
public readonly CefBrowser Browser;
public readonly ControlRenderHandler Renderer;
public LiveData(
OwnedTexture texture,
RobustCefClient client,
CefBrowser browser,
ControlRenderHandler renderer)
{
Texture = texture;
Client = client;
Browser = browser;
Renderer = renderer;
}
}
}
internal class ControlRenderHandler : CefRenderHandler
{
public ImageBuffer Buffer { get; }
private Control _control;
internal ControlRenderHandler(Control control)
{
Buffer = new ImageBuffer(control);
_control = control;
}
protected override CefAccessibilityHandler? GetAccessibilityHandler() => null;
protected override void GetViewRect(CefBrowser browser, out CefRectangle rect)
{
if (_control.Disposed)
{
rect = new CefRectangle();
return;
}
// TODO CEF: Do we need to pass real screen coords? Cause what we do already works...
//var screenCoords = _control.ScreenCoordinates;
//rect = new CefRectangle((int) screenCoords.X, (int) screenCoords.Y, (int)Math.Max(_control.Size.X, 1), (int)Math.Max(_control.Size.Y, 1));
// We do the max between size and 1 because it will LITERALLY CRASH WITHOUT AN ERROR otherwise.
rect = new CefRectangle(0, 0, (int) Math.Max(_control.Size.X, 1), (int) Math.Max(_control.Size.Y, 1));
}
protected override bool GetScreenInfo(CefBrowser browser, CefScreenInfo screenInfo)
{
if (_control.Disposed)
return false;
// TODO CEF: Get actual scale factor?
screenInfo.DeviceScaleFactor = 1.0f;
return true;
}
protected override void OnPopupSize(CefBrowser browser, CefRectangle rect)
{
if (_control.Disposed)
return;
}
protected override void OnPaint(CefBrowser browser, CefPaintElementType type, CefRectangle[] dirtyRects,
IntPtr buffer, int width, int height)
{
if (_control.Disposed)
return;
foreach (var dirtyRect in dirtyRects)
{
Buffer.UpdateBuffer(width, height, buffer, dirtyRect);
}
}
protected override void OnAcceleratedPaint(CefBrowser browser, CefPaintElementType type,
CefRectangle[] dirtyRects, IntPtr sharedHandle)
{
// Unused, but we're forced to implement it so.. NOOP.
}
protected override void OnScrollOffsetChanged(CefBrowser browser, double x, double y)
{
if (_control.Disposed)
return;
}
protected override void OnImeCompositionRangeChanged(CefBrowser browser, CefRange selectedRange,
CefRectangle[] characterBounds)
{
if (_control.Disposed)
return;
}
}
}

View File

@@ -0,0 +1,16 @@
namespace Robust.Client.CEF
{
public sealed class BrowserWindowCreateParameters
{
public int Width { get; set; }
public int Height { get; set; }
public string Url { get; set; } = "about:blank";
public BrowserWindowCreateParameters(int width, int height)
{
Width = width;
Height = height;
}
}
}

View File

@@ -0,0 +1,225 @@
using System.Diagnostics.CodeAnalysis;
namespace Robust.Client.CEF
{
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
[SuppressMessage("ReSharper", "IdentifierTypo")]
[SuppressMessage("ReSharper", "CA1069")]
[SuppressMessage("ReSharper", "CommentTypo")]
internal static class CefKeyCodes
{
// Taken from https://chromium.googlesource.com/chromium/src/+/refs/heads/main/ui/events/keycodes/keyboard_codes_posix.h
// See also https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
public enum ChromiumKeyboardCode
{
VKEY_CANCEL = 0x03,
VKEY_BACK = 0x08,
VKEY_TAB = 0x09,
VKEY_BACKTAB = 0x0A,
VKEY_CLEAR = 0x0C,
VKEY_RETURN = 0x0D,
VKEY_SHIFT = 0x10,
VKEY_CONTROL = 0x11,
VKEY_MENU = 0x12,
VKEY_PAUSE = 0x13,
VKEY_CAPITAL = 0x14,
VKEY_KANA = 0x15,
VKEY_HANGUL = 0x15,
VKEY_PASTE = 0x16,
VKEY_JUNJA = 0x17,
VKEY_FINAL = 0x18,
VKEY_HANJA = 0x19,
VKEY_KANJI = 0x19,
VKEY_ESCAPE = 0x1B,
VKEY_CONVERT = 0x1C,
VKEY_NONCONVERT = 0x1D,
VKEY_ACCEPT = 0x1E,
VKEY_MODECHANGE = 0x1F,
VKEY_SPACE = 0x20,
VKEY_PRIOR = 0x21,
VKEY_NEXT = 0x22,
VKEY_END = 0x23,
VKEY_HOME = 0x24,
VKEY_LEFT = 0x25,
VKEY_UP = 0x26,
VKEY_RIGHT = 0x27,
VKEY_DOWN = 0x28,
VKEY_SELECT = 0x29,
VKEY_PRINT = 0x2A,
VKEY_EXECUTE = 0x2B,
VKEY_SNAPSHOT = 0x2C, // Print Screen / SysRq
VKEY_INSERT = 0x2D,
VKEY_DELETE = 0x2E,
VKEY_HELP = 0x2F,
VKEY_0 = 0x30,
VKEY_1 = 0x31,
VKEY_2 = 0x32,
VKEY_3 = 0x33,
VKEY_4 = 0x34,
VKEY_5 = 0x35,
VKEY_6 = 0x36,
VKEY_7 = 0x37,
VKEY_8 = 0x38,
VKEY_9 = 0x39,
VKEY_A = 0x41,
VKEY_B = 0x42,
VKEY_C = 0x43,
VKEY_D = 0x44,
VKEY_E = 0x45,
VKEY_F = 0x46,
VKEY_G = 0x47,
VKEY_H = 0x48,
VKEY_I = 0x49,
VKEY_J = 0x4A,
VKEY_K = 0x4B,
VKEY_L = 0x4C,
VKEY_M = 0x4D,
VKEY_N = 0x4E,
VKEY_O = 0x4F,
VKEY_P = 0x50,
VKEY_Q = 0x51,
VKEY_R = 0x52,
VKEY_S = 0x53,
VKEY_T = 0x54,
VKEY_U = 0x55,
VKEY_V = 0x56,
VKEY_W = 0x57,
VKEY_X = 0x58,
VKEY_Y = 0x59,
VKEY_Z = 0x5A,
VKEY_LWIN = 0x5B,
VKEY_COMMAND = VKEY_LWIN, // Provide the Mac name for convenience.
VKEY_RWIN = 0x5C,
VKEY_APPS = 0x5D,
VKEY_SLEEP = 0x5F,
VKEY_NUMPAD0 = 0x60,
VKEY_NUMPAD1 = 0x61,
VKEY_NUMPAD2 = 0x62,
VKEY_NUMPAD3 = 0x63,
VKEY_NUMPAD4 = 0x64,
VKEY_NUMPAD5 = 0x65,
VKEY_NUMPAD6 = 0x66,
VKEY_NUMPAD7 = 0x67,
VKEY_NUMPAD8 = 0x68,
VKEY_NUMPAD9 = 0x69,
VKEY_MULTIPLY = 0x6A,
VKEY_ADD = 0x6B,
VKEY_SEPARATOR = 0x6C,
VKEY_SUBTRACT = 0x6D,
VKEY_DECIMAL = 0x6E,
VKEY_DIVIDE = 0x6F,
VKEY_F1 = 0x70,
VKEY_F2 = 0x71,
VKEY_F3 = 0x72,
VKEY_F4 = 0x73,
VKEY_F5 = 0x74,
VKEY_F6 = 0x75,
VKEY_F7 = 0x76,
VKEY_F8 = 0x77,
VKEY_F9 = 0x78,
VKEY_F10 = 0x79,
VKEY_F11 = 0x7A,
VKEY_F12 = 0x7B,
VKEY_F13 = 0x7C,
VKEY_F14 = 0x7D,
VKEY_F15 = 0x7E,
VKEY_F16 = 0x7F,
VKEY_F17 = 0x80,
VKEY_F18 = 0x81,
VKEY_F19 = 0x82,
VKEY_F20 = 0x83,
VKEY_F21 = 0x84,
VKEY_F22 = 0x85,
VKEY_F23 = 0x86,
VKEY_F24 = 0x87,
VKEY_NUMLOCK = 0x90,
VKEY_SCROLL = 0x91,
VKEY_LSHIFT = 0xA0,
VKEY_RSHIFT = 0xA1,
VKEY_LCONTROL = 0xA2,
VKEY_RCONTROL = 0xA3,
VKEY_LMENU = 0xA4,
VKEY_RMENU = 0xA5,
VKEY_BROWSER_BACK = 0xA6,
VKEY_BROWSER_FORWARD = 0xA7,
VKEY_BROWSER_REFRESH = 0xA8,
VKEY_BROWSER_STOP = 0xA9,
VKEY_BROWSER_SEARCH = 0xAA,
VKEY_BROWSER_FAVORITES = 0xAB,
VKEY_BROWSER_HOME = 0xAC,
VKEY_VOLUME_MUTE = 0xAD,
VKEY_VOLUME_DOWN = 0xAE,
VKEY_VOLUME_UP = 0xAF,
VKEY_MEDIA_NEXT_TRACK = 0xB0,
VKEY_MEDIA_PREV_TRACK = 0xB1,
VKEY_MEDIA_STOP = 0xB2,
VKEY_MEDIA_PLAY_PAUSE = 0xB3,
VKEY_MEDIA_LAUNCH_MAIL = 0xB4,
VKEY_MEDIA_LAUNCH_MEDIA_SELECT = 0xB5,
VKEY_MEDIA_LAUNCH_APP1 = 0xB6,
VKEY_MEDIA_LAUNCH_APP2 = 0xB7,
VKEY_OEM_1 = 0xBA,
VKEY_OEM_PLUS = 0xBB,
VKEY_OEM_COMMA = 0xBC,
VKEY_OEM_MINUS = 0xBD,
VKEY_OEM_PERIOD = 0xBE,
VKEY_OEM_2 = 0xBF,
VKEY_OEM_3 = 0xC0,
VKEY_OEM_4 = 0xDB,
VKEY_OEM_5 = 0xDC,
VKEY_OEM_6 = 0xDD,
VKEY_OEM_7 = 0xDE,
VKEY_OEM_8 = 0xDF,
VKEY_OEM_102 = 0xE2,
VKEY_OEM_103 = 0xE3, // GTV KEYCODE_MEDIA_REWIND
VKEY_OEM_104 = 0xE4, // GTV KEYCODE_MEDIA_FAST_FORWARD
VKEY_PROCESSKEY = 0xE5,
VKEY_PACKET = 0xE7,
VKEY_OEM_ATTN = 0xF0, // JIS DomKey::ALPHANUMERIC
VKEY_OEM_FINISH = 0xF1, // JIS DomKey::KATAKANA
VKEY_OEM_COPY = 0xF2, // JIS DomKey::HIRAGANA
VKEY_DBE_SBCSCHAR = 0xF3, // JIS DomKey::HANKAKU
VKEY_DBE_DBCSCHAR = 0xF4, // JIS DomKey::ZENKAKU
VKEY_OEM_BACKTAB = 0xF5, // JIS DomKey::ROMAJI
VKEY_ATTN = 0xF6, // DomKey::ATTN or JIS DomKey::KANA_MODE
VKEY_CRSEL = 0xF7,
VKEY_EXSEL = 0xF8,
VKEY_EREOF = 0xF9,
VKEY_PLAY = 0xFA,
VKEY_ZOOM = 0xFB,
VKEY_NONAME = 0xFC,
VKEY_PA1 = 0xFD,
VKEY_OEM_CLEAR = 0xFE,
VKEY_UNKNOWN = 0,
// POSIX specific VKEYs. Note that as of Windows SDK 7.1, 0x97-9F, 0xD8-DA,
// and 0xE8 are unassigned.
VKEY_WLAN = 0x97,
VKEY_POWER = 0x98,
VKEY_ASSISTANT = 0x99,
VKEY_SETTINGS = 0x9A,
VKEY_PRIVACY_SCREEN_TOGGLE = 0x9B,
VKEY_BRIGHTNESS_DOWN = 0xD8,
VKEY_BRIGHTNESS_UP = 0xD9,
VKEY_KBD_BRIGHTNESS_DOWN = 0xDA,
VKEY_KBD_BRIGHTNESS_UP = 0xE8,
// Windows does not have a specific key code for AltGr. We use the unused 0xE1
// (VK_OEM_AX) code to represent AltGr, matching the behaviour of Firefox on
// Linux.
VKEY_ALTGR = 0xE1,
// Windows does not have a specific key code for Compose. We use the unused
// 0xE6 (VK_ICO_CLEAR) code to represent Compose.
VKEY_COMPOSE = 0xE6,
// Windows does not have specific key codes for Media Play and Media Pause. We
// use the unused 0xE9 (VK_OEM_RESET) and 0xEA (VK_OEM_JUMP) codes to
// represent them.
VKEY_MEDIA_PLAY = 0xE9,
VKEY_MEDIA_PAUSE = 0xEA,
};
}
}

View File

@@ -0,0 +1,186 @@
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.ViewVariables;
using Xilium.CefGlue;
namespace Robust.Client.CEF
{
public partial class CefManager
{
[Dependency] private readonly IClydeInternal _clyde = default!;
private readonly List<BrowserWindowImpl> _browserWindows = new();
public IEnumerable<IBrowserWindow> AllBrowserWindows => _browserWindows;
public IBrowserWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams)
{
var mainHWnd = (_clyde.MainWindow as IClydeWindowInternal)?.WindowsHWnd ?? 0;
var info = CefWindowInfo.Create();
info.Width = createParams.Width;
info.Height = createParams.Height;
info.SetAsPopup(mainHWnd, "ss14cef");
var impl = new BrowserWindowImpl(this);
var lifeSpanHandler = new WindowLifeSpanHandler(impl);
var reqHandler = new RobustRequestHandler(Logger.GetSawmill("root"));
var client = new WindowCefClient(lifeSpanHandler, reqHandler);
var settings = new CefBrowserSettings();
impl.Browser = CefBrowserHost.CreateBrowserSync(info, client, settings, createParams.Url);
impl.RequestHandler = reqHandler;
_browserWindows.Add(impl);
return impl;
}
private sealed class BrowserWindowImpl : IBrowserWindow
{
private readonly CefManager _manager;
internal CefBrowser Browser = default!;
internal RobustRequestHandler RequestHandler = default!;
public Action<RequestHandlerContext>? OnResourceRequest { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public string Url
{
get
{
CheckClosed();
return Browser.GetMainFrame().Url;
}
set
{
CheckClosed();
Browser.GetMainFrame().LoadUrl(value);
}
}
[ViewVariables]
public bool IsLoading
{
get
{
CheckClosed();
return Browser.IsLoading;
}
}
public BrowserWindowImpl(CefManager manager)
{
_manager = manager;
}
public void StopLoad()
{
CheckClosed();
Browser.StopLoad();
}
public void Reload()
{
CheckClosed();
Browser.Reload();
}
public bool GoBack()
{
CheckClosed();
if (!Browser.CanGoBack)
return false;
Browser.GoBack();
return true;
}
public bool GoForward()
{
CheckClosed();
if (!Browser.CanGoForward)
return false;
Browser.GoForward();
return true;
}
public void ExecuteJavaScript(string code)
{
CheckClosed();
Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1);
}
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
{
RequestHandler.AddResourceRequestHandler(handler);
}
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
{
RequestHandler.RemoveResourceRequestHandler(handler);
}
public void Dispose()
{
if (Closed)
return;
Browser.GetHost().CloseBrowser(true);
Closed = true;
}
public bool Closed { get; private set; }
public void OnClose()
{
Closed = true;
_manager._browserWindows.Remove(this);
Logger.Debug("Removing window");
}
private void CheckClosed()
{
if (Closed)
throw new ObjectDisposedException("BrowserWindow");
}
}
private sealed class WindowCefClient : CefClient
{
private readonly CefLifeSpanHandler _lifeSpanHandler;
private readonly CefRequestHandler _requestHandler;
public WindowCefClient(CefLifeSpanHandler lifeSpanHandler, CefRequestHandler requestHandler)
{
_lifeSpanHandler = lifeSpanHandler;
_requestHandler = requestHandler;
}
protected override CefLifeSpanHandler GetLifeSpanHandler() => _lifeSpanHandler;
protected override CefRequestHandler GetRequestHandler() => _requestHandler;
}
private sealed class WindowLifeSpanHandler : CefLifeSpanHandler
{
private readonly BrowserWindowImpl _windowImpl;
public WindowLifeSpanHandler(BrowserWindowImpl windowImpl)
{
_windowImpl = windowImpl;
}
protected override void OnBeforeClose(CefBrowser browser)
{
base.OnBeforeClose(browser);
_windowImpl.OnClose();
}
}
}
}

View File

@@ -0,0 +1,104 @@
using System;
using System.IO;
using JetBrains.Annotations;
using Robust.Shared.ContentPack;
using Robust.Shared.Log;
using Robust.Shared.Utility;
// The library we're using right now. TODO CEF: Do we want to use something else? We will need to ship it ourselves if so.
using Xilium.CefGlue;
namespace Robust.Client.CEF
{
// Register this with IoC.
// TODO CEF: think if making this inherit CefApp is a good idea...
// TODO CEF: A way to handle external window browsers...
[UsedImplicitly]
public partial class CefManager
{
private CefApp _app = default!;
private bool _initialized = false;
/// <summary>
/// Call this to initialize CEF.
/// </summary>
public void Initialize()
{
DebugTools.Assert(!_initialized);
string subProcessName;
if (OperatingSystem.IsWindows())
subProcessName = "Robust.Client.CEF.exe";
else if (OperatingSystem.IsLinux())
subProcessName = "Robust.Client.CEF";
else
throw new NotSupportedException("Unsupported platform for CEF!");
var subProcessPath = PathHelpers.ExecutableRelativeFile(subProcessName);
var settings = new CefSettings()
{
WindowlessRenderingEnabled = true, // So we can render to our UI controls.
ExternalMessagePump = false, // Unsure, honestly. TODO CEF: Research this?
NoSandbox = true, // Not disabling the sandbox crashes CEF.
BrowserSubprocessPath = subProcessPath,
LocalesDirPath = Path.Combine(PathHelpers.GetExecutableDirectory(), "locales"),
ResourcesDirPath = PathHelpers.GetExecutableDirectory(),
RemoteDebuggingPort = 9222
};
Logger.Info($"CEF Version: {CefRuntime.ChromeVersion}");
// --------------------------- README --------------------------------------------------
// By the way! You're gonna need the CEF binaries in your client's bin folder.
// More specifically, version cef_binary_91.1.21+g9dd45fe+chromium-91.0.4472.114
// https://cef-builds.spotifycdn.com/cef_binary_91.1.21%2Bg9dd45fe%2Bchromium-91.0.4472.114_windows64_minimal.tar.bz2
// https://cef-builds.spotifycdn.com/cef_binary_91.1.21%2Bg9dd45fe%2Bchromium-91.0.4472.114_linux64_minimal.tar.bz2
// Here's how to get it to work:
// 1. Copy all the contents of "Release" to the bin folder.
// 2. Copy all the contents of "Resources" to the bin folder.
// Supposedly, you should just need libcef.so in Release and icudtl.dat in Resources...
// The rest might be optional.
// Maybe. Good luck! If you get odd crashes with no info and a weird exit code, use GDB!
// -------------------------------------------------------------------------------------
_app = new RobustCefApp();
// We pass no main arguments...
CefRuntime.Initialize(new CefMainArgs(null), settings, _app, IntPtr.Zero);
// TODO CEF: After this point, debugging breaks. No, literally. My client crashes but ONLY with the debugger.
// I have tried using the DEBUG and RELEASE versions of libcef.so, stripped or non-stripped...
// And nothing seemed to work. Odd.
_initialized = true;
}
public void CheckInitialized()
{
if (!_initialized)
throw new InvalidOperationException("CefManager has not been initialized!");
}
/// <summary>
/// Needs to be called regularly for CEF to keep working.
/// </summary>
public void Update()
{
DebugTools.Assert(_initialized);
// Calling this makes CEF do its work, without using its own update loop.
CefRuntime.DoMessageLoopWork();
}
/// <summary>
/// Call before program shutdown.
/// </summary>
public void Shutdown()
{
DebugTools.Assert(_initialized);
CefRuntime.Shutdown();
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
namespace Robust.Client.CEF
{
public interface IBrowserControl
{
/// <summary>
/// Current URL of the browser. Set to load a new page.
/// </summary>
string Url { get; set; }
/// <summary>
/// Whether the browser is currently loading a page.
/// </summary>
bool IsLoading { get; }
/// <summary>
/// Stops loading the current page.
/// </summary>
void StopLoad();
/// <summary>
/// Reload the current page.
/// </summary>
void Reload();
/// <summary>
/// Navigate back.
/// </summary>
/// <returns>Whether the browser could navigate back.</returns>
bool GoBack();
/// <summary>
/// Navigate forward.
/// </summary>
/// <returns>Whether the browser could navigate forward.</returns>
bool GoForward();
/// <summary>
/// Execute arbitrary JavaScript on the current page.
/// </summary>
/// <param name="code">JavaScript code.</param>
void ExecuteJavaScript(string code);
void AddResourceRequestHandler(Action<RequestHandlerContext> handler);
void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler);
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Robust.Client.CEF
{
public interface IBrowserWindow : IBrowserControl, IDisposable
{
bool Closed { get; }
}
}

View File

@@ -0,0 +1,42 @@
using System;
using Robust.Client.UserInterface;
using Robust.Client.Utility;
using Robust.Shared.Maths;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using Xilium.CefGlue;
namespace Robust.Client.CEF
{
internal sealed class ImageBuffer
{
private readonly Control _control;
public ImageBuffer(Control control)
{
_control = control;
}
public Image<Bgra32> Buffer { get; private set; } = new(1, 1);
public unsafe void UpdateBuffer(int width, int height, IntPtr buffer, CefRectangle dirtyRect)
{
if (width != Buffer.Width || height != Buffer.Height)
UpdateSize(width, height);
var span = new ReadOnlySpan<Bgra32>((void*) buffer, width * height);
ImageSharpExt.Blit(
span,
width,
UIBox2i.FromDimensions(dirtyRect.X, dirtyRect.Y, dirtyRect.Width, dirtyRect.Height),
Buffer,
(dirtyRect.X, dirtyRect.Y));
}
private void UpdateSize(int width, int height)
{
Buffer = new Image<Bgra32>(width, height);
}
}
}

View File

@@ -0,0 +1,33 @@
using System;
using Xilium.CefGlue;
namespace Robust.Client.CEF
{
public static class Program
{
// This was supposed to be the main entry for the subprocess program... It doesn't work.
public static int Main(string[] args)
{
// This is a workaround for this to work on UNIX.
var argv = args;
if (CefRuntime.Platform != CefRuntimePlatform.Windows)
{
argv = new string[args.Length + 1];
Array.Copy(args, 0, argv, 1, args.Length);
argv[0] = "-";
}
var mainArgs = new CefMainArgs(argv);
// This will block executing until the subprocess is shut down.
var code = CefRuntime.ExecuteProcess(mainArgs, null, IntPtr.Zero);
if (code != 0)
{
System.Console.WriteLine($"CEF Subprocess exited unsuccessfully with exit code {code}! Arguments: {string.Join(' ', argv)}");
}
return code;
}
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.IO;
using System.Net;
using Xilium.CefGlue;
namespace Robust.Client.CEF
{
public sealed class RequestHandlerContext
{
internal readonly CefRequest CefRequest;
public bool IsNavigation { get; }
public bool IsDownload { get; }
public string RequestInitiator { get; }
public string Url => CefRequest.Url;
public string Method => CefRequest.Method;
public bool IsHandled { get; private set; }
public bool IsCancelled { get; private set; }
internal IRequestResult? Result { get; private set; }
internal RequestHandlerContext(
bool isNavigation,
bool isDownload,
string requestInitiator,
CefRequest cefRequest)
{
CefRequest = cefRequest;
IsNavigation = isNavigation;
IsDownload = isDownload;
RequestInitiator = requestInitiator;
}
public void DoCancel()
{
CheckNotHandled();
IsHandled = true;
IsCancelled = true;
}
public void DoRespondStream(Stream stream, string contentType, HttpStatusCode code = HttpStatusCode.OK)
{
Result = new RequestResultStream(stream, contentType, code);
}
private void CheckNotHandled()
{
if (IsHandled)
throw new InvalidOperationException("Request has already been handled");
}
}
}

View File

@@ -0,0 +1,93 @@
using System;
using System.IO;
using System.Net;
using Xilium.CefGlue;
namespace Robust.Client.CEF
{
internal interface IRequestResult
{
CefResourceHandler MakeHandler();
}
internal sealed class RequestResultStream : IRequestResult
{
private readonly Stream _stream;
private readonly HttpStatusCode _code;
private readonly string _contentType;
public RequestResultStream(Stream stream, string contentType, HttpStatusCode code)
{
_stream = stream;
_code = code;
_contentType = contentType;
}
public CefResourceHandler MakeHandler()
{
return new Handler(_stream, _contentType, _code);
}
private sealed class Handler : CefResourceHandler
{
// TODO: async
// TODO: exception handling
private readonly Stream _stream;
private readonly HttpStatusCode _code;
private readonly string _contentType;
public Handler(Stream stream, string contentType, HttpStatusCode code)
{
_stream = stream;
_code = code;
_contentType = contentType;
}
protected override bool Open(CefRequest request, out bool handleRequest, CefCallback callback)
{
handleRequest = true;
return true;
}
protected override void GetResponseHeaders(CefResponse response, out long responseLength, out string? redirectUrl)
{
response.Status = (int) _code;
response.StatusText = _code.ToString();
response.MimeType = _contentType;
if (_stream.CanSeek)
responseLength = _stream.Length;
else
responseLength = -1;
redirectUrl = default;
}
protected override bool Skip(long bytesToSkip, out long bytesSkipped, CefResourceSkipCallback callback)
{
if (!_stream.CanSeek)
{
bytesSkipped = -2;
return false;
}
bytesSkipped = _stream.Seek(bytesToSkip, SeekOrigin.Begin);
return true;
}
protected override unsafe bool Read(IntPtr dataOut, int bytesToRead, out int bytesRead, CefResourceReadCallback callback)
{
var byteSpan = new Span<byte>((void*) dataOut, bytesToRead);
bytesRead = _stream.Read(byteSpan);
return bytesRead != 0;
}
protected override void Cancel()
{
}
}
}
}

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\MSBuild\Robust.Properties.targets" />
<Import Project="..\MSBuild\Robust.Engine.props" />
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>WinExe</OutputType>
</PropertyGroup>
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
<Target Name="RobustAfterBuild" AfterTargets="Build" />
<Import Project="..\MSBuild\Robust.Engine.targets" />
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" />
<PackageReference Include="System.Drawing.Common" Version="5.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\cefglue\CefGlue\CefGlue.csproj" />
<ProjectReference Include="..\Robust.Client\Robust.Client.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,48 @@
using System;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Xilium.CefGlue;
namespace Robust.Client.CEF
{
internal class RobustCefApp : CefApp
{
private readonly BrowserProcessHandler _browserProcessHandler = new();
private readonly RenderProcessHandler _renderProcessHandler = new();
protected override CefBrowserProcessHandler GetBrowserProcessHandler()
{
return _browserProcessHandler;
}
protected override CefRenderProcessHandler GetRenderProcessHandler()
{
return _renderProcessHandler;
}
protected override void OnBeforeCommandLineProcessing(string processType, CefCommandLine commandLine)
{
// Disable zygote on Linux.
commandLine.AppendSwitch("--no-zygote");
//commandLine.AppendSwitch("--disable-gpu");
//commandLine.AppendSwitch("--disable-gpu-compositing");
//commandLine.AppendSwitch("--in-process-gpu");
commandLine.AppendSwitch("disable-threaded-scrolling", "1");
commandLine.AppendSwitch("disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents");
if(IoCManager.Instance != null)
Logger.Debug($"{commandLine}");
}
private class BrowserProcessHandler : CefBrowserProcessHandler
{
}
// TODO CEF: Research - Is this even needed?
private class RenderProcessHandler : CefRenderProcessHandler
{
}
}
}

View File

@@ -0,0 +1,23 @@
using Xilium.CefGlue;
namespace Robust.Client.CEF
{
// Simple CEF client.
internal class RobustCefClient : CefClient
{
private readonly CefRenderHandler _renderHandler;
private readonly CefRequestHandler _requestHandler;
private readonly CefLoadHandler _loadHandler;
internal RobustCefClient(CefRenderHandler handler, CefRequestHandler requestHandler, CefLoadHandler loadHandler)
{
_renderHandler = handler;
_requestHandler = requestHandler;
_loadHandler = loadHandler;
}
protected override CefRenderHandler GetRenderHandler() => _renderHandler;
protected override CefRequestHandler GetRequestHandler() => _requestHandler;
protected override CefLoadHandler GetLoadHandler() => _loadHandler;
}
}

View File

@@ -0,0 +1,17 @@
using Xilium.CefGlue;
namespace Robust.Client.CEF
{
public sealed class RobustLoadHandler : CefLoadHandler
{
protected override void OnLoadStart(CefBrowser browser, CefFrame frame, CefTransitionType transitionType)
{
base.OnLoadStart(browser, frame, transitionType);
}
protected override void OnLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode)
{
base.OnLoadEnd(browser, frame, httpStatusCode);
}
}
}

View File

@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Log;
using Xilium.CefGlue;
namespace Robust.Client.CEF
{
internal sealed class RobustRequestHandler : CefRequestHandler
{
private readonly ISawmill _sawmill;
private readonly List<Action<RequestHandlerContext>> _resourceRequestHandlers = new();
private readonly List<Action<BeforeBrowseContext>> _beforeBrowseHandlers = new();
public RobustRequestHandler(ISawmill sawmill)
{
_sawmill = sawmill;
}
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
{
lock (_resourceRequestHandlers)
{
_resourceRequestHandlers.Add(handler);
}
}
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
{
lock (_resourceRequestHandlers)
{
_resourceRequestHandlers.Remove(handler);
}
}
public void AddBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
{
lock (_beforeBrowseHandlers)
{
_beforeBrowseHandlers.Add(handler);
}
}
public void RemoveBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
{
lock (_beforeBrowseHandlers)
{
_beforeBrowseHandlers.Remove(handler);
}
}
protected override CefResourceRequestHandler? GetResourceRequestHandler(
CefBrowser browser,
CefFrame frame,
CefRequest request,
bool isNavigation,
bool isDownload,
string requestInitiator,
ref bool disableDefaultHandling)
{
lock (_resourceRequestHandlers)
{
_sawmill.Debug($"HANDLING REQUEST: {request.Url}");
var context = new RequestHandlerContext(isNavigation, isDownload, requestInitiator, request);
foreach (var handler in _resourceRequestHandlers)
{
handler(context);
if (context.IsHandled)
disableDefaultHandling = true;
if (context.IsCancelled)
return null;
if (context.Result != null)
return new WrapReaderResourceHandler(context.Result.MakeHandler());
}
}
return null;
}
protected override bool OnBeforeBrowse(CefBrowser browser, CefFrame frame, CefRequest request, bool userGesture, bool isRedirect)
{
lock (_beforeBrowseHandlers)
{
var context = new BeforeBrowseContext(isRedirect, userGesture, request);
foreach (var handler in _beforeBrowseHandlers)
{
handler(context);
if (context.IsCancelled)
return true;
}
}
return false;
}
private sealed class WrapReaderResourceHandler : CefResourceRequestHandler
{
private readonly CefResourceHandler _handler;
public WrapReaderResourceHandler(CefResourceHandler handler)
{
_handler = handler;
}
protected override CefCookieAccessFilter? GetCookieAccessFilter(
CefBrowser browser,
CefFrame frame,
CefRequest request)
{
return null;
}
protected override CefResourceHandler GetResourceHandler(
CefBrowser browser,
CefFrame frame,
CefRequest request)
{
return _handler;
}
}
}
}

View File

@@ -29,26 +29,27 @@ namespace Robust.Build.Tasks
}
}
//formatted according to https://github.com/dotnet/msbuild/blob/main/src/Shared/CanonicalError.cs#L57
class ConsoleBuildEngine : IBuildEngine
{
public void LogErrorEvent(BuildErrorEventArgs e)
{
Console.WriteLine($"ERROR: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL ERROR {e.Code}: {e.Message}");
}
public void LogWarningEvent(BuildWarningEventArgs e)
{
Console.WriteLine($"WARNING: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL WARNING {e.Code}: {e.Message}");
}
public void LogMessageEvent(BuildMessageEventArgs e)
{
Console.WriteLine($"MESSAGE: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
Console.WriteLine($"{e.File} ({e.LineNumber},{e.ColumnNumber},{e.EndLineNumber},{e.EndColumnNumber}): XAMLIL MESSAGE {e.Code}: {e.Message}");
}
public void LogCustomEvent(CustomBuildEventArgs e)
{
Console.WriteLine($"CUSTOM: {e.Message}");
Console.WriteLine(e.Message);
}
public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties,

View File

@@ -1,4 +1,6 @@
using System.Linq;
using System.Diagnostics;
using System.Linq;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
@@ -11,11 +13,14 @@ namespace Robust.Build.Tasks
/// Emitters & Transformers based on:
/// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs
/// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
/// - https://github.com/AvaloniaUI/Avalonia/blob/afb8ae6f3c517dae912729511483995b16cb31af/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
/// </summary>
public class RobustXamlILCompiler : XamlILCompiler
{
public RobustXamlILCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings, bool fillWithDefaults) : base(configuration, emitMappings, fillWithDefaults)
{
Transformers.Insert(0, new IgnoredDirectivesTransformer());
Transformers.Add(new AddNameScopeRegistration());
Transformers.Add(new RobustMarkRootObjectScopeNode());
@@ -197,5 +202,24 @@ namespace Robust.Build.Tasks
}
}
}
class IgnoredDirectivesTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlAstObjectNode astNode)
{
astNode.Children.RemoveAll(n =>
n is XamlAstXmlDirective dir &&
dir.Namespace == XamlNamespaces.Xaml2006 &&
(dir.Name == "Class" ||
dir.Name == "Precompile" ||
dir.Name == "FieldModifier" ||
dir.Name == "ClassModifier"));
}
return node;
}
}
}
}

View File

@@ -280,8 +280,8 @@ namespace Robust.Build.Tasks
}
catch (Exception e)
{
engine.LogWarningEvent(new BuildWarningEventArgs("XAMLIL", "", res.Uri, 0, 0, 0, 0,
e.ToString(), "", "CompileRobustXaml"));
engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0,
$"{res.FilePath}: {e.Message}", "", "CompileRobustXaml"));
}
}
return true;

View File

@@ -65,7 +65,7 @@ namespace Robust.Client.Audio.Midi
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
private SharedBroadphaseSystem _broadPhaseSystem = default!;
[ViewVariables]
public bool IsAvailable
@@ -175,7 +175,7 @@ namespace Robust.Client.Audio.Midi
_midiThread = new Thread(ThreadUpdate);
_midiThread.Start();
_broadPhaseSystem = EntitySystem.Get<SharedBroadPhaseSystem>();
_broadPhaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
FluidsynthInitialized = true;
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Net;
using Robust.Client.Debugging;
using Robust.Client.GameObjects;

View File

@@ -25,6 +25,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
@@ -76,6 +77,7 @@ namespace Robust.Client
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IMidiManager, MidiManager>();
IoCManager.Register<IAuthManager, AuthManager>();
IoCManager.Register<IPhysicsManager, PhysicsManager>();
switch (mode)
{
case GameController.DisplayMode.Headless:

View File

@@ -83,6 +83,8 @@ namespace Robust.Client.Console
OutputText(text, true, true);
}
public override event ConAnyCommandCallback? AnyCommandExecuted;
/// <inheritdoc />
public override void ExecuteCommand(ICommonSession? session, string command)
{
@@ -103,7 +105,11 @@ namespace Robust.Client.Console
{
var command1 = AvailableCommands[commandName];
args.RemoveAt(0);
command1.Execute(new ConsoleShell(this, null), command, args.ToArray());
var shell = new ConsoleShell(this, null);
var cmdArgs = args.ToArray();
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
command1.Execute(shell, command, cmdArgs);
}
else
WriteError(null, "Unknown command: " + commandName);

View File

@@ -27,6 +27,7 @@ using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.Console.Commands
{
@@ -459,13 +460,18 @@ namespace Robust.Client.Console.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var root = IoCManager.Resolve<IUserInterfaceManager>().RootControl;
var uiMgr = IoCManager.Resolve<IUserInterfaceManager>();
var res = IoCManager.Resolve<IResourceManager>();
using (var stream = res.UserData.Create(new ResourcePath("/guidump.txt")))
using (var writer = new StreamWriter(stream, EncodingHelpers.UTF8))
{
_writeNode(root, 0, writer);
foreach (var root in uiMgr.AllRoots)
{
writer.WriteLine($"ROOT: {root}");
_writeNode(root, 0, writer);
writer.WriteLine("---------------");
}
}
shell.WriteLine("Saved guidump");
@@ -475,7 +481,7 @@ namespace Robust.Client.Console.Commands
{
var indentation = new string(' ', indents * 2);
writer.WriteLine("{0}{1}", indentation, control);
foreach (var (key, value) in _propertyValuesFor(control))
foreach (var (key, value) in PropertyValuesFor(control))
{
writer.WriteLine("{2} * {0}: {1}", key, value, indentation);
}
@@ -486,14 +492,14 @@ namespace Robust.Client.Console.Commands
}
}
private static List<(string, string)> _propertyValuesFor(Control control)
internal static List<(string, string)> PropertyValuesFor(Control control)
{
var members = new List<(string, string)>();
var type = control.GetType();
foreach (var fieldInfo in type.GetAllFields())
{
if (fieldInfo.GetCustomAttribute<ViewVariablesAttribute>() == null)
if (!ViewVariablesUtility.TryGetViewVariablesAccess(fieldInfo, out _))
{
continue;
}
@@ -503,7 +509,7 @@ namespace Robust.Client.Console.Commands
foreach (var propertyInfo in type.GetAllProperties())
{
if (propertyInfo.GetCustomAttribute<ViewVariablesAttribute>() == null)
if (!ViewVariablesUtility.TryGetViewVariablesAccess(propertyInfo, out _))
{
continue;
}
@@ -536,7 +542,10 @@ namespace Robust.Client.Console.Commands
var scroll = new ScrollContainer();
tabContainer.AddChild(scroll);
//scroll.SetAnchorAndMarginPreset(Control.LayoutPreset.Wide);
var vBox = new VBoxContainer();
var vBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
scroll.AddChild(vBox);
var progressBar = new ProgressBar { MaxValue = 10, Value = 5 };
@@ -594,7 +603,10 @@ namespace Robust.Client.Console.Commands
}
var group = new ButtonGroup();
var vBoxRadioButtons = new VBoxContainer();
var vBoxRadioButtons = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
for (var i = 0; i < 10; i++)
{
vBoxRadioButtons.AddChild(new Button
@@ -610,8 +622,9 @@ namespace Robust.Client.Console.Commands
TabContainer.SetTabTitle(vBoxRadioButtons, "Radio buttons!!");
tabContainer.AddChild(new VBoxContainer
tabContainer.AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Name = "Slider",
Children =
{
@@ -863,7 +876,7 @@ namespace Robust.Client.Console.Commands
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
var chunk = internalGrid.GetChunk(chunkIndex);
shell.WriteLine($"worldBounds: {chunk.CalcWorldBounds()} localBounds: {chunk.CalcLocalBounds()}");
shell.WriteLine($"worldBounds: {chunk.CalcWorldAABB()} localBounds: {chunk.CalcLocalBounds()}");
}
}

View File

@@ -0,0 +1,17 @@
using Robust.Client.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
namespace Robust.Client.Console.Commands
{
public class GridChunkBBCommand : IConsoleCommand
{
public string Command => "showchunkbb";
public string Description => "Displays chunk bounds for the purposes of rendering";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<GridChunkBoundsDebugSystem>().Enabled ^= true;
}
}
}

View File

@@ -13,10 +13,11 @@ using Microsoft.CodeAnalysis.Text;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.ViewVariables;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
using Robust.Shared.Scripting;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using Color = Robust.Shared.Maths.Color;
#nullable enable
@@ -116,7 +117,7 @@ namespace Robust.Client.Console
}
else
{
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager);
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager).AddReferences(typeof(Image).Assembly);
newScript = CSharpScript.Create(code, options, typeof(ScriptGlobals));
}

View File

@@ -11,6 +11,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Scripting;
using Robust.Shared.Timing;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.Console
{
@@ -18,7 +19,7 @@ namespace Robust.Client.Console
{
private readonly IReflectionManager _reflectionManager;
private readonly VBoxContainer _watchesVBox;
private readonly BoxContainer _watchesVBox;
private readonly LineEdit _addWatchEdit;
private readonly Button _addWatchButton;
@@ -31,17 +32,20 @@ namespace Robust.Client.Console
Title = "Watch Window";
var mainVBox = new VBoxContainer
var mainVBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
MinSize = (500, 300),
Children =
{
(_watchesVBox = new VBoxContainer
(_watchesVBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
VerticalExpand = true
}),
new HBoxContainer
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
(_addWatchEdit = new HistoryLineEdit
@@ -105,8 +109,9 @@ namespace Robust.Client.Console
Button delButton;
_runner = runner;
AddChild(new HBoxContainer
AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
(_outputLabel = new Label
@@ -166,8 +171,9 @@ namespace Robust.Client.Console
public CompilationErrorControl(string message)
{
Button delButton;
AddChild(new HBoxContainer
AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
new Label

View File

@@ -162,12 +162,13 @@ 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))
foreach (var physBody in EntitySystem.Get<SharedBroadphaseSystem>().GetCollidingEntities(mapId, viewport))
{
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
// all entities have a TransformComponent
var transform = physBody.Owner.Transform;

View File

@@ -28,6 +28,7 @@ using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Dynamics.Contacts;
@@ -40,6 +41,8 @@ namespace Robust.Client.Debugging
* Used for debugging shapes, controllers, joints, contacts
*/
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
private const int MaxContactPoints = 2048;
internal int PointCount;
@@ -79,8 +82,7 @@ namespace Robust.Client.Debugging
CollisionManager.GetPointStates(out state1, out state2, oldManifold, manifold);
Span<Vector2> points = stackalloc Vector2[2];
Vector2 normal;
contact.GetWorldManifold(out normal, points);
contact.GetWorldManifold(_physicsManager, out var normal, points);
for (int i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
{

View File

@@ -255,6 +255,15 @@ namespace Robust.Client
_configurationManager.OverrideConVars(_commandLineArgs.CVars);
}
{
// Handle GameControllerOptions implicit CVar overrides.
_configurationManager.OverrideConVars(new []
{
(CVars.DisplayWindowIconSet.Name, options.WindowIconSet.ToString()),
(CVars.DisplaySplashLogo.Name, options.SplashLogo.ToString())
});
}
ProfileOptSetup.Setup(_configurationManager);
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
@@ -472,6 +481,8 @@ namespace Robust.Client
internal void Cleanup()
{
_modLoader.Shutdown();
_networkManager.Shutdown("Client shutting down");
_midiManager.Shutdown();
IoCManager.Resolve<IEntityLookup>().Shutdown();

View File

@@ -52,6 +52,16 @@ namespace Robust.Client
/// </summary>
public ResourcePath PrototypeDirectory { get; init; } = new(@"/Prototypes/");
/// <summary>
/// Directory resource path containing window icons to load.
/// </summary>
public ResourcePath WindowIconSet { get; init; } = new("/Textures/Logo/icon");
/// <summary>
/// Resource path for splash image to show when the game starts up.
/// </summary>
public ResourcePath SplashLogo { get; init; } = new("/Textures/Logo/logo.png");
/// <summary>
/// Whether to disable mounting the "Resources/" folder on FULL_RELEASE.
/// </summary>

View File

@@ -25,6 +25,7 @@ namespace Robust.Client.GameObjects
RegisterClass<InputComponent>();
RegisterClass<SpriteComponent>();
RegisterClass<ClientOccluderComponent>();
RegisterClass<OccluderTreeComponent>();
RegisterClass<EyeComponent>();
RegisterClass<AppearanceComponent>();
RegisterClass<AppearanceTestComponent>();

View File

@@ -1,7 +1,5 @@
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;
@@ -10,7 +8,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Defines data fields used in the <see cref="InputSystem"/>.
/// </summary>
class InputComponent : Component
public class InputComponent : Component
{
/// <inheritdoc />
public override string Name => "Input";

View File

@@ -228,6 +228,6 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Calculate sprite bounding box in world-space coordinates.
/// </summary>
Box2 CalculateBoundingBox();
Box2 CalculateBoundingBox(Vector2 worldPos);
}
}

View File

@@ -0,0 +1,90 @@
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Graphics.Clyde;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
public sealed class ShowSpriteBBCommand : IConsoleCommand
{
public string Command => "showspritebb";
public string Description => "Toggle whether sprite bounds are shown";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<SpriteBoundsSystem>().Enabled ^= true;
}
}
public sealed class SpriteBoundsSystem : EntitySystem
{
private SpriteBoundsOverlay? _overlay;
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
if (_enabled)
{
DebugTools.AssertNull(_overlay);
var overlayManager = IoCManager.Resolve<IOverlayManager>();
_overlay = new SpriteBoundsOverlay(EntitySystem.Get<RenderingTreeSystem>(), IoCManager.Resolve<IEyeManager>());
overlayManager.AddOverlay(_overlay);
}
else
{
if (_overlay == null) return;
var overlayManager = IoCManager.Resolve<IOverlayManager>();
overlayManager.RemoveOverlay(_overlay);
_overlay = null;
}
}
}
private bool _enabled;
}
public class SpriteBoundsOverlay : Overlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly IEyeManager _eyeManager = default!;
private RenderingTreeSystem _renderTree;
public SpriteBoundsOverlay(RenderingTreeSystem renderTree, IEyeManager eyeManager)
{
_renderTree = renderTree;
_eyeManager = eyeManager;
}
protected internal override void Draw(in OverlayDrawArgs args)
{
var handle = args.WorldHandle;
var currentMap = _eyeManager.CurrentMap;
var viewport = _eyeManager.GetWorldViewport();
foreach (var comp in _renderTree.GetRenderTrees(currentMap, viewport))
{
var localAABB = viewport.Translated(-comp.Owner.Transform.WorldPosition);
foreach (var sprite in comp.SpriteTree.QueryAabb(localAABB))
{
var worldPos = sprite.Owner.Transform.WorldPosition;
var bounds = sprite.CalculateBoundingBox(worldPos);
handle.DrawRect(bounds, Color.Red.WithAlpha(0.2f));
handle.DrawRect(bounds.Scale(0.2f).Translated(-new Vector2(0f, bounds.Extents.Y)), Color.Blue.WithAlpha(0.5f));
}
}
}
}
}

View File

@@ -365,7 +365,7 @@ namespace Robust.Client.GameObjects
}
else
{
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath);
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'.", rsiPath);
}
}
}
@@ -1212,9 +1212,9 @@ namespace Robust.Client.GameObjects
public IEnumerable<ISpriteLayer> AllLayers => Layers;
// Lobby SpriteView rendering path
internal void Render(DrawingHandleWorld drawingHandle, Angle worldRotation, Direction? overrideDirection = null)
internal void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null)
{
RenderInternal(drawingHandle, worldRotation, Vector2.Zero, overrideDirection);
RenderInternal(drawingHandle, eyeRotation, worldRotation, Vector2.Zero, overrideDirection);
}
[DataField("noRot")]
@@ -1239,7 +1239,7 @@ namespace Robust.Client.GameObjects
public bool EnableDirectionOverride { get => _enableOverrideDirection; set => _enableOverrideDirection = value; }
// Sprite rendering path
internal void Render(DrawingHandleWorld drawingHandle, in Angle worldRotation, in Vector2 worldPosition)
internal void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, in Angle worldRotation, in Vector2 worldPosition)
{
Direction? overrideDir = null;
if (_enableOverrideDirection)
@@ -1247,16 +1247,18 @@ namespace Robust.Client.GameObjects
overrideDir = _overrideDirection;
}
RenderInternal(drawingHandle, worldRotation, worldPosition, overrideDir);
RenderInternal(drawingHandle, eyeRotation, worldRotation, worldPosition, overrideDir);
}
private void CalcModelMatrix(int numDirs, Angle worldRotation, Vector2 worldPosition, out Matrix3 modelMatrix)
private void CalcModelMatrix(int numDirs, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, out Matrix3 modelMatrix)
{
Angle angle;
if (_screenLock)
{
angle = Angle.Zero;
// Negate the eye rotation in the model matrix, so that later when the view matrix is applied the
// sprite will be locked upright to the screen
angle = new Angle(-eyeRotation.Theta);
}
else
{
@@ -1267,7 +1269,7 @@ namespace Robust.Client.GameObjects
modelMatrix = Matrix3.CreateTransform(in worldPosition, in sWorldRotation);
}
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection)
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection)
{
var localMatrix = GetLocalMatrix();
@@ -1280,7 +1282,7 @@ namespace Robust.Client.GameObjects
var numDirs = GetLayerDirectionCount(layer);
CalcModelMatrix(numDirs, worldRotation, worldPosition, out var modelMatrix);
CalcModelMatrix(numDirs, eyeRotation, worldRotation, worldPosition, out var modelMatrix);
Matrix3.Multiply(ref localMatrix, ref modelMatrix, out var transformMatrix);
drawingHandle.SetTransform(in transformMatrix);
@@ -1510,6 +1512,11 @@ namespace Robust.Client.GameObjects
private void QueueUpdateIsInert()
{
// Look this was an easy way to get bounds checks for layer updates.
// If you really want it optimal you'll need to comb through all 2k lines of spritecomponent.
if (Owner?.EntityManager?.EventBus != null)
UpdateBounds();
if (_inertUpdateQueued)
return;
@@ -1624,11 +1631,11 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc/>
public Box2 CalculateBoundingBox()
public Box2 CalculateBoundingBox(Vector2 worldPos)
{
// fast check for empty sprites
if (Layers.Count == 0)
return new Box2();
return new Box2(worldPos, worldPos);
// we need to calculate bounding box taking into account all nested layers
// because layers can have offsets, scale or rotation we need to calculate a new BB
@@ -1651,11 +1658,15 @@ namespace Robust.Client.GameObjects
new Box2Rotated(spriteBox, Rotation).CalcBoundingBox() : spriteBox;
// move it all to world transform system (with sprite offset)
var worldPosition = Owner.Transform.WorldPosition;
var worldBB = spriteBB.Translated(Offset + worldPosition);
var worldBB = spriteBB.Translated(Offset + worldPos);
return worldBB;
}
internal void UpdateBounds()
{
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
}
/// <summary>
/// Enum to "offset" a cardinal direction.
/// </summary>
@@ -1711,7 +1722,19 @@ namespace Robust.Client.GameObjects
public bool AutoAnimated = true;
[ViewVariables(VVAccess.ReadWrite)]
public Vector2 Offset { get; set; }
public Vector2 Offset
{
get => _offset;
set
{
if (_offset.EqualsApprox(value)) return;
_offset = value;
_parent.UpdateBounds();
}
}
private Vector2 _offset;
[ViewVariables]
public DirectionOffset DirOffset { get; set; }
@@ -1983,7 +2006,6 @@ namespace Robust.Client.GameObjects
// TODO: scale & rotation for layers is currently unimplemented.
return Box2.CenteredAround(Offset, PixelSize / EyeManager.PixelsPerMeter);
}
}
void IAnimationProperties.SetAnimatableProperty(string name, object value)

View File

@@ -15,29 +15,45 @@ namespace Robust.Client.GameObjects
private static Box2 SpriteAabbFunc(in SpriteComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
return new Box2(worldPos, worldPos);
var bounds = value.CalculateBoundingBox(worldPos);
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var offset = -tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return bounds.Translated(offset);
}
private static Box2 LightAabbFunc(in PointLightComponent value)
{
var worldPos = value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var boxSize = value.Radius * 2;
return Box2.CenteredAround(worldPos, (boxSize, boxSize));
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return Box2.CenteredAround(pos, (boxSize, boxSize));
}
internal static Box2 SpriteAabbFunc(SpriteComponent value, Vector2? worldPos = null)
{
worldPos ??= value.Owner.Transform.WorldPosition;
var bounds = value.CalculateBoundingBox(worldPos.Value);
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
return new Box2(worldPos.Value, worldPos.Value);
var offset = -tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return bounds.Translated(offset);
}
internal static Box2 LightAabbFunc(PointLightComponent value, Vector2? worldPos = null)
{
worldPos ??= value.Owner.Transform.WorldPosition;
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
var boxSize = value.Radius * 2;
return Box2.CenteredAround(worldPos.Value, (boxSize, boxSize));
var pos = worldPos - tree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
return Box2.CenteredAround(pos, (boxSize, boxSize));
}
}
}

View File

@@ -23,8 +23,7 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IClydeAudio _clyde = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
[Dependency] private readonly SharedBroadphaseSystem _broadPhaseSystem = default!;
private readonly List<PlayingStream> _playingClydeStreams = new();
@@ -38,7 +37,6 @@ namespace Robust.Client.GameObjects
SubscribeNetworkEvent<StopAudioMessageClient>(StopAudioMessageHandler);
SubscribeLocalEvent<SoundSystem.QueryAudioSystem>((ev => ev.Audio = this));
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
}
private void StopAudioMessageHandler(StopAudioMessageClient ev)
@@ -301,7 +299,7 @@ namespace Robust.Client.GameObjects
if (!source.SetPosition(coordinates.ToMapPos(EntityManager)))
{
source.Dispose();
Logger.Warning("Can't play positional audio \"{stream.Name}\", can't set position.");
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
return null;
}

View File

@@ -1,94 +0,0 @@
using System.Collections.Generic;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
public class ClientContainerSystem : ContainerSystem
{
private readonly HashSet<IEntity> _updateQueue = new();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<UpdateContainerOcclusionMessage>(UpdateContainerOcclusion);
UpdatesBefore.Add(typeof(SpriteSystem));
}
private void UpdateContainerOcclusion(UpdateContainerOcclusionMessage ev)
{
_updateQueue.Add(ev.Entity);
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
foreach (var toUpdate in _updateQueue)
{
if (toUpdate.Deleted)
{
continue;
}
UpdateEntityRecursively(toUpdate);
}
_updateQueue.Clear();
}
private static void UpdateEntityRecursively(IEntity entity)
{
// TODO: Since we are recursing down,
// we could cache ShowContents data here to speed it up for children.
// Am lazy though.
UpdateEntity(entity);
foreach (var child in entity.Transform.Children)
{
UpdateEntityRecursively(child.Owner);
}
}
private static void UpdateEntity(IEntity entity)
{
if (entity.TryGetComponent(out SpriteComponent? sprite))
{
sprite.ContainerOccluded = false;
// We have to recursively scan for containers upwards in case of nested containers.
var tempParent = entity;
while (tempParent.TryGetContainer(out var container))
{
if (!container.ShowContents)
{
sprite.ContainerOccluded = true;
break;
}
tempParent = container.Owner;
}
}
if (entity.TryGetComponent(out PointLightComponent? light))
{
light.ContainerOccluded = false;
// We have to recursively scan for containers upwards in case of nested containers.
var tempParent = entity;
while (tempParent.TryGetContainer(out var container))
{
if (container.OccludesLight)
{
light.ContainerOccluded = true;
break;
}
tempParent = container.Owner;
}
}
}
}
}

View File

@@ -62,7 +62,7 @@ namespace Robust.Client.GameObjects
}
}
private static void HandleAnchorChanged(EntityUid uid, ClientOccluderComponent component, AnchorStateChangedEvent args)
private static void HandleAnchorChanged(EntityUid uid, ClientOccluderComponent component, ref AnchorStateChangedEvent args)
{
component.AnchorStateChanged();
}

View File

@@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using static Robust.Shared.Containers.ContainerManagerComponent;
namespace Robust.Client.GameObjects
{
public class ContainerSystem : SharedContainerSystem
{
[Dependency] private readonly IRobustSerializer _serializer = default!;
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
private readonly HashSet<IEntity> _updateQueue = new();
public readonly Dictionary<EntityUid, IContainer> ExpectedEntities = new();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<UpdateContainerOcclusionMessage>(UpdateContainerOcclusion);
SubscribeLocalEvent<EntityInitializedMessage>(HandleEntityInitialized);
SubscribeLocalEvent<ContainerManagerComponent, ComponentHandleState>(HandleComponentState);
UpdatesBefore.Add(typeof(SpriteSystem));
}
private void UpdateContainerOcclusion(UpdateContainerOcclusionMessage ev)
{
_updateQueue.Add(ev.Entity);
}
private void HandleEntityInitialized(EntityInitializedMessage ev)
{
if (!ExpectedEntities.TryGetValue(ev.Entity.Uid, out var container))
return;
RemoveExpectedEntity(ev.Entity.Uid);
if (container.Deleted)
return;
container.Insert(ev.Entity);
}
private void HandleComponentState(EntityUid uid, ContainerManagerComponent component, ComponentHandleState args)
{
if (args.Current is not ContainerManagerComponentState cast)
return;
// Delete now-gone containers.
List<string>? toDelete = null;
foreach (var (id, container) in component.Containers)
{
if (!cast.ContainerSet.Any(data => data.Id == id))
{
container.Shutdown();
toDelete ??= new List<string>();
toDelete.Add(id);
}
}
if (toDelete != null)
{
foreach (var dead in toDelete)
{
component.Containers.Remove(dead);
}
}
// Add new containers and update existing contents.
foreach (var (containerType, id, showEnts, occludesLight, entityUids) in cast.ContainerSet)
{
if (!component.Containers.TryGetValue(id, out var container))
{
container = ContainerFactory(component, containerType, id);
component.Containers.Add(id, container);
}
// sync show flag
container.ShowContents = showEnts;
container.OccludesLight = occludesLight;
// Remove gone entities.
List<IEntity>? toRemove = null;
foreach (var entity in container.ContainedEntities)
{
if (!entityUids.Contains(entity.Uid))
{
toRemove ??= new List<IEntity>();
toRemove.Add(entity);
}
}
if (toRemove != null)
{
foreach (var goner in toRemove)
container.Remove(goner);
}
// Remove entities that were expected, but have been removed from the container.
List<EntityUid>? removedExpected = null;
foreach (var entityUid in container.ExpectedEntities)
{
if (!entityUids.Contains(entityUid))
{
removedExpected ??= new List<EntityUid>();
removedExpected.Add(entityUid);
}
}
if (removedExpected != null)
{
foreach (var entityUid in removedExpected)
RemoveExpectedEntity(entityUid);
}
// Add new entities.
foreach (var entityUid in entityUids)
{
if (!EntityManager.TryGetEntity(entityUid, out var entity))
{
AddExpectedEntity(entityUid, container);
continue;
}
if (!container.ContainedEntities.Contains(entity))
container.Insert(entity);
}
}
}
private IContainer ContainerFactory(ContainerManagerComponent component, string containerType, string id)
{
var type = _serializer.FindSerializedType(typeof(IContainer), containerType);
if (type is null) throw new ArgumentException($"Container of type {containerType} for id {id} cannot be found.");
var newContainer = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type);
newContainer.ID = id;
newContainer.Manager = component;
return newContainer;
}
public void AddExpectedEntity(EntityUid uid, IContainer container)
{
if (ExpectedEntities.ContainsKey(uid))
return;
ExpectedEntities.Add(uid, container);
container.ExpectedEntities.Add(uid);
}
public void RemoveExpectedEntity(EntityUid uid)
{
if (!ExpectedEntities.TryGetValue(uid, out var container))
return;
ExpectedEntities.Remove(uid);
container.ExpectedEntities.Remove(uid);
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
foreach (var toUpdate in _updateQueue)
{
if (toUpdate.Deleted)
{
continue;
}
UpdateEntityRecursively(toUpdate);
}
_updateQueue.Clear();
}
private static void UpdateEntityRecursively(IEntity entity)
{
// TODO: Since we are recursing down,
// we could cache ShowContents data here to speed it up for children.
// Am lazy though.
UpdateEntity(entity);
foreach (var child in entity.Transform.Children)
{
UpdateEntityRecursively(child.Owner);
}
}
private static void UpdateEntity(IEntity entity)
{
if (entity.TryGetComponent(out SpriteComponent? sprite))
{
sprite.ContainerOccluded = false;
// We have to recursively scan for containers upwards in case of nested containers.
var tempParent = entity;
while (tempParent.TryGetContainer(out var container))
{
if (!container.ShowContents)
{
sprite.ContainerOccluded = true;
break;
}
tempParent = container.Owner;
}
}
if (entity.TryGetComponent(out PointLightComponent? light))
{
light.ContainerOccluded = false;
// We have to recursively scan for containers upwards in case of nested containers.
var tempParent = entity;
while (tempParent.TryGetContainer(out var container))
{
if (container.OccludesLight)
{
light.ContainerOccluded = true;
break;
}
tempParent = container.Owner;
}
}
}
}
}

View File

@@ -0,0 +1,90 @@
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
public class GridChunkBoundsDebugSystem : EntitySystem
{
private GridChunkBoundsOverlay? _overlay;
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
if (_enabled)
{
DebugTools.Assert(_overlay == null);
_overlay = new GridChunkBoundsOverlay(
IoCManager.Resolve<IEntityManager>(),
IoCManager.Resolve<IEyeManager>(),
IoCManager.Resolve<IMapManager>());
IoCManager.Resolve<IOverlayManager>().AddOverlay(_overlay);
}
else
{
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(_overlay!);
_overlay = null;
}
}
}
private bool _enabled;
}
internal sealed class GridChunkBoundsOverlay : Overlay
{
private readonly IEntityManager _entityManager;
private readonly IEyeManager _eyeManager;
private readonly IMapManager _mapManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public GridChunkBoundsOverlay(IEntityManager entManager, IEyeManager eyeManager, IMapManager mapManager)
{
_entityManager = entManager;
_eyeManager = eyeManager;
_mapManager = mapManager;
}
protected internal override void Draw(in OverlayDrawArgs args)
{
var currentMap = _eyeManager.CurrentMap;
var viewport = _eyeManager.GetWorldViewport();
foreach (var grid in _mapManager.FindGridsIntersecting(currentMap, viewport))
{
var mapGrid = (IMapGridInternal) grid;
var gridEnt = _entityManager.GetEntity(grid.GridEntityId);
var worldPos = gridEnt.Transform.WorldPosition;
var worldRot = gridEnt.Transform.WorldRotation;
foreach (var (_, chunk) in mapGrid.GetMapChunks())
{
var chunkBounds = chunk.CalcWorldBounds(worldPos, worldRot);
var aabb = chunkBounds.CalcBoundingBox();
// Calc world bounds for chunk.
if (!aabb.Intersects(in viewport))
{
continue;
}
args.WorldHandle.DrawRect(chunkBounds, Color.Green.WithAlpha(0.2f), true);
args.WorldHandle.DrawRect(aabb, Color.Red, false);
}
}
}
}
}

View File

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

View File

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

View File

@@ -41,22 +41,12 @@ namespace Robust.Client.GameObjects
{
if (mapId == MapId.Nullspace) yield break;
var enclosed = false;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
yield return EntityManager.GetEntity(grid.GridEntityId).GetComponent<RenderingTreeComponent>();
// if we're enclosed then we know no other grids relevant + don't need the map's rendertree
if (grid.WorldBounds.Encloses(in worldAABB))
{
enclosed = true;
break;
}
}
if (!enclosed)
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
yield return _mapManager.GetMapEntity(mapId).GetComponent<RenderingTreeComponent>();
}
internal IEnumerable<DynamicTree<SpriteComponent>> GetSpriteTrees(MapId mapId, Box2 worldAABB)
@@ -118,7 +108,7 @@ namespace Robust.Client.GameObjects
QueueSpriteUpdate(component);
}
private void AnythingMoved(MoveEvent args)
private void AnythingMoved(ref MoveEvent args)
{
AnythingMovedSubHandler(args.Sender.Transform);
}
@@ -156,7 +146,7 @@ namespace Robust.Client.GameObjects
QueueSpriteUpdate(component);
}
private void SpriteParentChanged(EntityUid uid, SpriteComponent component, EntParentChangedMessage args)
private void SpriteParentChanged(EntityUid uid, SpriteComponent component, ref EntParentChangedMessage args)
{
QueueSpriteUpdate(component);
}
@@ -189,7 +179,7 @@ namespace Robust.Client.GameObjects
QueueLightUpdate(component);
}
private void LightParentChanged(EntityUid uid, PointLightComponent component, EntParentChangedMessage args)
private void LightParentChanged(EntityUid uid, PointLightComponent component, ref EntParentChangedMessage args)
{
QueueLightUpdate(component);
}
@@ -259,9 +249,8 @@ namespace Robust.Client.GameObjects
EntityManager.GetEntity(_mapManager.GetGrid(gridId).GridEntityId).EnsureComponent<RenderingTreeComponent>();
}
private RenderingTreeComponent? GetRenderTree(IEntity entity)
internal static RenderingTreeComponent? GetRenderTree(IEntity entity)
{
if (entity.Transform.MapID == MapId.Nullspace ||
entity.HasComponent<RenderingTreeComponent>()) return null;
@@ -278,6 +267,11 @@ namespace Robust.Client.GameObjects
return null;
}
private bool IsVisible(SpriteComponent component)
{
return component.Visible && !component.ContainerOccluded;
}
public override void FrameUpdate(float frameTime)
{
_checkedChildren.Clear();
@@ -285,7 +279,7 @@ namespace Robust.Client.GameObjects
foreach (var sprite in _spriteQueue)
{
sprite.TreeUpdateQueued = false;
if (!sprite.Visible || sprite.ContainerOccluded)
if (!IsVisible(sprite))
{
ClearSprite(sprite);
continue;
@@ -302,8 +296,7 @@ namespace Robust.Client.GameObjects
continue;
}
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var aabb = RenderingTreeComponent.SpriteAabbFunc(sprite, worldPos).Translated(-treePos);
var aabb = RenderingTreeComponent.SpriteAabbFunc(sprite, worldPos);
// If we're on a new map then clear the old one.
if (oldMapTree != newMapTree)
@@ -348,7 +341,7 @@ namespace Robust.Client.GameObjects
}
var treePos = newMapTree?.Owner.Transform.WorldPosition ?? Vector2.Zero;
var aabb = RenderingTreeComponent.LightAabbFunc(light, worldPos).Translated(-treePos);
var aabb = RenderingTreeComponent.LightAabbFunc(light, worldPos);
// If we're on a new map then clear the old one.
if (oldMapTree != newMapTree)

View File

@@ -15,15 +15,14 @@ namespace Robust.Client.GameObjects
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly RenderingTreeSystem _treeSystem = default!;
private RenderingTreeSystem _treeSystem = default!;
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
public override void Initialize()
{
base.Initialize();
_treeSystem = Get<RenderingTreeSystem>();
SubscribeLocalEvent<SpriteUpdateInertEvent>(QueueUpdateInert);
}

View File

@@ -111,10 +111,11 @@ namespace Robust.Client.Graphics
public MapCoordinates ScreenToMap(ScreenCoordinates point)
{
var (pos, window) = point;
if (window != MainViewport.Window?.Id)
if (_uiManager.MouseGetControl(point) is not IViewportControl viewport)
return default;
return MainViewport.ScreenToMap(pos);
return viewport.ScreenToMap(pos);
}
/// <inheritdoc />

View File

@@ -53,7 +53,7 @@ namespace Robust.Client.Graphics
ScreenCoordinates CoordinatesToScreen(EntityCoordinates point);
/// <summary>
/// Unprojects a point from UI screen space to world space using the current camera.
/// Unprojects a point from UI screen space to world space using the viewport under the screen coordinates.
/// </summary>
/// <remarks>
/// The game exists on the 2D X/Y plane, so this function returns a point o the plane
@@ -64,7 +64,7 @@ namespace Robust.Client.Graphics
MapCoordinates ScreenToMap(ScreenCoordinates point);
/// <summary>
/// Unprojects a point from UI screen space to world space using the current camera.
/// Unprojects a point from UI screen space to world space using the main viewport.
/// </summary>
/// <remarks>
/// The game exists on the 2D X/Y plane, so this function returns a point o the plane

View File

@@ -62,7 +62,7 @@ namespace Robust.Client.Graphics.Clyde
TextEntered?.Invoke(args);
break;
case DEventWindowClosed(var reg, var args):
reg.Closed?.Invoke(args);
reg.RequestClosed?.Invoke(args);
CloseWindow?.Invoke(args);
if (reg.DisposeOnClose && !reg.IsMainWindow)
@@ -95,7 +95,7 @@ namespace Robust.Client.Graphics.Clyde
_eventDispatchQueue.Enqueue(new DEventScroll(ev));
}
private void SendCloseWindow(WindowReg windowReg, WindowClosedEventArgs ev)
private void SendCloseWindow(WindowReg windowReg, WindowRequestClosedEventArgs ev)
{
_eventDispatchQueue.Enqueue(new DEventWindowClosed(windowReg, ev));
}
@@ -156,7 +156,7 @@ namespace Robust.Client.Graphics.Clyde
private sealed record DEventScroll(MouseWheelEventArgs Args) : DEventBase;
private sealed record DEventWindowClosed(WindowReg Reg, WindowClosedEventArgs Args) : DEventBase;
private sealed record DEventWindowClosed(WindowReg Reg, WindowRequestClosedEventArgs Args) : DEventBase;
private sealed record DEventWindowResized(WindowResizedEventArgs Args) : DEventBase;

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -19,18 +20,17 @@ namespace Robust.Client.Graphics.Clyde
private int _verticesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
private int _indicesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
private void _drawGrids(Box2 worldBounds)
private void _drawGrids(Viewport viewport, Box2 worldBounds, IEye eye)
{
var mapId = _eyeManager.CurrentMap;
var mapId = eye.Position.MapId;
if (!_mapManager.MapExists(mapId))
{
// fall back to the default eye's map
_eyeManager.ClearCurrentEye();
mapId = _eyeManager.CurrentMap;
// fall back to nullspace map
mapId = MapId.Nullspace;
}
SetTexture(TextureUnit.Texture0, _tileDefinitionManager.TileTextureAtlas);
SetTexture(TextureUnit.Texture1, _lightingReady ? _currentViewport!.LightRenderTarget.Texture : _stockTextureWhite);
SetTexture(TextureUnit.Texture1, _lightingReady ? viewport.LightRenderTarget.Texture : _stockTextureWhite);
var (gridProgram, _) = ActivateShaderInstance(_defaultShader.Handle);
SetupGlobalUniformsImmediate(gridProgram, (ClydeTexture) _tileDefinitionManager.TileTextureAtlas);
@@ -53,14 +53,8 @@ namespace Robust.Client.Graphics.Clyde
var transform = compMan.GetComponent<ITransformComponent>(grid.GridEntityId);
gridProgram.SetUniform(UniIModelMatrix, transform.WorldMatrix);
foreach (var (_, chunk) in grid.GetMapChunks())
foreach (var chunk in grid.GetMapChunks(worldBounds))
{
// Calc world bounds for chunk.
if (!chunk.CalcWorldBounds().Intersects(in worldBounds))
{
continue;
}
if (_isChunkDirty(grid, chunk))
{
_updateChunkMesh(grid, chunk);
@@ -210,11 +204,14 @@ namespace Robust.Client.Graphics.Clyde
private void _updateOnGridCreated(MapId mapId, GridId gridId)
{
Logger.DebugS("grid", $"Adding {gridId} to grid renderer");
_mapChunkData.Add(gridId, new Dictionary<Vector2i, MapChunkData>());
}
private void _updateOnGridRemoved(MapId mapId, GridId gridId)
{
Logger.DebugS("grid", $"Removing {gridId} from grid renderer");
var data = _mapChunkData[gridId];
foreach (var chunkDatum in data.Values)
{

View File

@@ -9,6 +9,7 @@ using Robust.Shared.Maths;
using Robust.Shared.Utility;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared;
using Robust.Shared.Enums;
namespace Robust.Client.Graphics.Clyde
@@ -201,9 +202,10 @@ namespace Robust.Client.Graphics.Clyde
}
private void DrawEntities(Viewport viewport, Box2 worldBounds)
private void DrawEntities(Viewport viewport, Box2 worldBounds, IEye eye)
{
if (_eyeManager.CurrentMap == MapId.Nullspace || !_mapManager.HasMapEntity(_eyeManager.CurrentMap))
var mapId = eye.Position.MapId;
if (mapId == MapId.Nullspace || !_mapManager.HasMapEntity(mapId))
{
return;
}
@@ -212,12 +214,7 @@ namespace Robust.Client.Graphics.Clyde
var screenSize = viewport.Size;
// So we could calculate the correct size of the entities based on the contents of their sprite...
// Or we can just assume that no entity is larger than 10x10 and get a stupid easy check.
// TODO: Make this check more accurate.
var widerBounds = worldBounds.Enlarged(5);
ProcessSpriteEntities(_eyeManager.CurrentMap, widerBounds, _drawingSpriteList);
ProcessSpriteEntities(mapId, worldBounds, _drawingSpriteList);
var worldOverlays = new List<Overlay>();
@@ -273,13 +270,15 @@ namespace Robust.Client.Graphics.Clyde
break;
}
var matrix = entry.worldMatrix;
var worldPosition = new Vector2(matrix.R0C2, matrix.R1C2);
RenderTexture? entityPostRenderTarget = null;
Vector2i roundedPos = default;
if (entry.sprite.PostShader != null)
{
// calculate world bounding box
var spriteBB = entry.sprite.CalculateBoundingBox();
var spriteBB = entry.sprite.CalculateBoundingBox(worldPosition);
var spriteLB = spriteBB.BottomLeft;
var spriteRT = spriteBB.TopRight;
@@ -291,6 +290,9 @@ namespace Robust.Client.Graphics.Clyde
// scale can be passed with PostShader as variable in future
var postShadeScale = 1.25f;
var screenSpriteSize = (Vector2i) ((screenRT - screenLB) * postShadeScale).Rounded();
// Rotate the vector by the eye angle, otherwise the bounding box will be incorrect
screenSpriteSize = (Vector2i) eye.Rotation.RotateVec(screenSpriteSize).Rounded();
screenSpriteSize.Y = -screenSpriteSize.Y;
// I'm not 100% sure why it works, but without it post-shader
@@ -323,9 +325,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
var matrix = entry.worldMatrix;
var worldPosition = new Vector2(matrix.R0C2, matrix.R1C2);
entry.sprite.Render(_renderHandle.DrawingHandleWorld, in entry.worldRotation, in worldPosition);
entry.sprite.Render(_renderHandle.DrawingHandleWorld, eye.Rotation, in entry.worldRotation, in worldPosition);
if (entry.sprite.PostShader != null && entityPostRenderTarget != null)
{
@@ -370,7 +370,7 @@ namespace Robust.Client.Graphics.Clyde
{
var bounds = worldBounds.Translated(-comp.Owner.Transform.WorldPosition);
comp.SpriteTree.QueryAabb(ref list, ((
comp.SpriteTree.QueryAabb(ref list, (
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
in SpriteComponent value) =>
{
@@ -381,17 +381,20 @@ namespace Robust.Client.Graphics.Clyde
entry.sprite = value;
entry.worldRot = transform.WorldRotation;
entry.matrix = transform.WorldMatrix;
var worldPos = entry.matrix.Transform(transform.LocalPosition);
entry.yWorldPos = worldPos.Y;
var worldPos = new Vector2(entry.matrix.R0C2, entry.matrix.R1C2);
// Didn't use the bounds from the query as that has to be re-calculated (and is probably more expensive than this).
var bounds = value.CalculateBoundingBox(worldPos);
entry.yWorldPos = worldPos.Y - bounds.Extents.Y;
return true;
}), bounds, true);
}, bounds);
}
}
private void DrawSplash(IRenderHandle handle)
{
var texture = _resourceCache.GetResource<TextureResource>("/Textures/Logo/logo.png").Texture;
var splashTex = _cfg.GetCVar(CVars.DisplaySplashLogo);
var texture = _resourceCache.GetResource<TextureResource>(splashTex).Texture;
handle.DrawingHandleScreen.DrawTexture(texture, (ScreenSize - texture.Size) / 2);
}
@@ -467,13 +470,13 @@ namespace Robust.Client.Graphics.Clyde
using (DebugGroup("Grids"))
{
_drawGrids(worldBounds);
_drawGrids(viewport, worldBounds, eye);
}
// We will also render worldspace overlays here so we can do them under / above entities as necessary
using (DebugGroup("Entities"))
{
DrawEntities(viewport, worldBounds);
DrawEntities(viewport, worldBounds, eye);
}
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowFOV, worldBounds);

View File

@@ -5,6 +5,7 @@ using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -323,11 +324,17 @@ namespace Robust.Client.Graphics.Clyde
return;
}
var map = eye.Position.MapId;
var mapId = eye.Position.MapId;
var (lights, count, expandedBounds) = GetLightsToRender(map, worldBounds);
// If this map has lighting disabled, return
if (!_mapManager.GetMapEntity(mapId).GetComponent<IMapComponent>().LightingEnabled)
{
return;
}
UpdateOcclusionGeometry(map, expandedBounds, eye.Position.Position);
var (lights, count, expandedBounds) = GetLightsToRender(mapId, worldBounds);
UpdateOcclusionGeometry(mapId, expandedBounds, eye.Position.Position);
DrawFov(viewport, eye);
@@ -773,24 +780,13 @@ namespace Robust.Client.Graphics.Clyde
var ii = 0;
var imi = 0;
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, expandedBounds, true))
foreach (var comp in occluderSystem.GetOccluderTrees(map, expandedBounds))
{
if (!occluderSystem.TryGetOccluderTreeForGrid(map, gridId, out var occluderTree)) continue;
// TODO: I know this doesn't work with rotated grids but when I come back to these I'm adding tests
// because rotation bugs are common.
var treeBounds = expandedBounds.Translated(-comp.Owner.Transform.WorldPosition);
Box2 gridBounds;
if (gridId == GridId.Invalid)
{
gridBounds = expandedBounds;
}
else
{
// TODO: Ideally this would clamp to the outer border of what we can see
var grid = _mapManager.GetGrid(gridId);
gridBounds = expandedBounds.Translated(-grid.WorldPosition);
}
occluderTree.QueryAabb((in OccluderComponent sOccluder) =>
comp.Tree.QueryAabb((in OccluderComponent sOccluder) =>
{
var occluder = (ClientOccluderComponent)sOccluder;
var transform = occluder.Owner.Transform;
@@ -919,7 +915,7 @@ namespace Robust.Client.Graphics.Clyde
ami += 4;
return true;
}, gridBounds);
}, treeBounds);
}
_occlusionDataLength = ii;

View File

@@ -153,6 +153,7 @@ namespace Robust.Client.Graphics.Clyde
// Draw the entity.
sprite.Render(
DrawingHandleWorld,
Angle.Zero,
overrideDirection == null
? entity.Transform.WorldRotation
: Angle.Zero,

View File

@@ -7,6 +7,7 @@ using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.GameObjects;
using Robust.Client.Utility;
using Robust.Shared;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Color = Robust.Shared.Maths.Color;
@@ -1078,7 +1079,7 @@ namespace Robust.Client.Graphics.Clyde
var a = _drawList[x].Item1;
var b = _drawList[y].Item1;
var cmp = (a.DrawDepth).CompareTo(b.DrawDepth);
var cmp = a.DrawDepth.CompareTo(b.DrawDepth);
if (cmp != 0)
{
return cmp;
@@ -1091,7 +1092,7 @@ namespace Robust.Client.Graphics.Clyde
return cmp;
}
cmp = _drawList[x].Item4.CompareTo(_drawList[y].Item4);
cmp = _drawList[y].Item4.CompareTo(_drawList[x].Item4);
if (cmp != 0)
{

View File

@@ -1,7 +1,9 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.Utility;
@@ -153,6 +155,10 @@ namespace Robust.Client.Graphics.Clyde
{
isActuallySrgb = loadParams.Srgb;
}
else if (pixelType == typeof(Bgra32))
{
isActuallySrgb = loadParams.Srgb;
}
else if (pixelType == typeof(A8))
{
DebugTools.Assert(_hasGLTextureSwizzle);
@@ -258,6 +264,7 @@ namespace Robust.Client.Graphics.Clyde
// Note that if _hasGLSrgb is off, we import an sRGB texture as non-sRGB.
// Shaders are expected to compensate for this
Rgba32 => (srgb && _hasGLSrgb ? PIF.Srgb8Alpha8 : PIF.Rgba8, PF.Rgba, PT.UnsignedByte),
Bgra32 => (srgb && _hasGLSrgb ? PIF.Srgb8Alpha8 : PIF.Rgba8, PF.Bgra, PT.UnsignedByte),
A8 or L8 => (PIF.R8, PF.Red, PT.UnsignedByte),
_ => throw new NotSupportedException("Unsupported pixel type."),
};
@@ -315,27 +322,80 @@ namespace Robust.Client.Graphics.Clyde
private unsafe void SetSubImage<T>(
ClydeTexture texture,
Vector2i dstTl,
Image<T> srcImage,
Image<T> img,
in UIBox2i srcBox)
where T : unmanaged, IPixel<T>
{
if (!_hasGLTextureSwizzle)
if (srcBox.Left < 0 ||
srcBox.Top < 0 ||
srcBox.Right > srcBox.Width ||
srcBox.Bottom > srcBox.Height)
{
if (typeof(T) == typeof(A8))
{
SetSubImage(texture, dstTl, ApplyA8Swizzle((Image<A8>) (object) srcImage), srcBox);
return;
}
throw new ArgumentOutOfRangeException(nameof(srcBox), "Source rectangle out of bounds.");
}
if (typeof(T) == typeof(L8))
{
SetSubImage(texture, dstTl, ApplyL8Swizzle((Image<L8>) (object) srcImage), srcBox);
return;
}
var size = srcBox.Width * srcBox.Height;
T[]? pooled = null;
// C# won't let me use an if due to the stackalloc.
var copyBuffer = size < 16 * 16
? stackalloc T[size]
: (pooled = ArrayPool<T>.Shared.Rent(size)).AsSpan(0, size);
var srcSpan = img.GetPixelSpan();
var w = img.Width;
FlipCopySubRegion(srcBox, w, srcSpan, copyBuffer);
SetSubImageImpl<T>(texture, dstTl, (srcBox.Width, srcBox.Height), copyBuffer);
if (pooled != null)
ArrayPool<T>.Shared.Return(pooled);
}
private unsafe void SetSubImage<T>(
ClydeTexture texture,
Vector2i dstTl,
Vector2i size,
ReadOnlySpan<T> buf)
where T : unmanaged, IPixel<T>
{
T[]? pooled = null;
// C# won't let me use an if due to the stackalloc.
var copyBuffer = buf.Length < 16 * 16
? stackalloc T[buf.Length]
: (pooled = ArrayPool<T>.Shared.Rent(buf.Length)).AsSpan(0, buf.Length);
FlipCopy(buf, copyBuffer, size.X, size.Y);
SetSubImageImpl<T>(texture, dstTl, size, copyBuffer);
if (pooled != null)
ArrayPool<T>.Shared.Return(pooled);
}
private unsafe void SetSubImageImpl<T>(
ClydeTexture texture,
Vector2i dstTl,
Vector2i size,
ReadOnlySpan<T> buf)
where T : unmanaged, IPixel<T>
{
if (!_hasGLTextureSwizzle && typeof(T) == typeof(A8) && typeof(T) == typeof(L8))
{
var swizzleBuf = ArrayPool<Rgba32>.Shared.Rent(buf.Length);
var destSpan = swizzleBuf.AsSpan(0, buf.Length);
if (typeof(T) == typeof(A8))
ApplyA8Swizzle(MemoryMarshal.Cast<T, A8>(buf), destSpan);
else if (typeof(T) == typeof(L8))
ApplyL8Swizzle(MemoryMarshal.Cast<T, L8>(buf), destSpan);
SetSubImageImpl<Rgba32>(texture, dstTl, size, destSpan);
ArrayPool<Rgba32>.Shared.Return(swizzleBuf);
return;
}
var loaded = _loadedTextures[texture.TextureId];
var pixType = GetTexturePixelType<T>();
if (pixType != loaded.TexturePixelType)
@@ -346,11 +406,8 @@ namespace Robust.Client.Graphics.Clyde
throw new InvalidOperationException("Mismatching pixel type for texture.");
}
if (loaded.Width < dstTl.X + srcBox.Width || loaded.Height < dstTl.Y + srcBox.Height)
throw new ArgumentOutOfRangeException(nameof(srcBox), "Destination rectangle out of bounds.");
if (srcBox.Left < 0 || srcBox.Top < 0 || srcBox.Right > srcImage.Width || srcBox.Bottom > srcImage.Height)
throw new ArgumentOutOfRangeException(nameof(srcBox), "Source rectangle out of bounds.");
if (loaded.Width < dstTl.X + size.X || loaded.Height < dstTl.Y + size.Y)
throw new ArgumentOutOfRangeException(nameof(size), "Destination rectangle out of bounds.");
if (sizeof(T) != 4)
{
@@ -364,24 +421,14 @@ namespace Robust.Client.Graphics.Clyde
GL.BindTexture(TextureTarget.Texture2D, loaded.OpenGLObject.Handle);
CheckGlError();
var size = srcBox.Width * srcBox.Height;
var copyBuffer = size < 16 * 16 ? stackalloc T[size] : new T[size];
for (var y = 0; y < srcBox.Height; y++)
for (var x = 0; x < srcBox.Width; x++)
fixed (T* aPtr = buf)
{
copyBuffer[(srcBox.Height - y - 1) * srcBox.Width + x] = srcImage[x + srcBox.Left, srcBox.Top + y];
}
fixed (T* aPtr = copyBuffer)
{
var dstY = loaded.Height - dstTl.Y - srcBox.Height;
var dstY = loaded.Height - dstTl.Y - size.Y;
GL.TexSubImage2D(
TextureTarget.Texture2D,
0,
dstTl.X, dstY,
srcBox.Width, srcBox.Height,
size.X, size.Y,
pf, pt,
(IntPtr) aPtr);
CheckGlError();
@@ -398,7 +445,7 @@ namespace Robust.Client.Graphics.Clyde
{
return default(T) switch
{
Rgba32 => TexturePixelType.Rgba32,
Rgba32 or Bgra32 => TexturePixelType.Rgba32,
L8 => TexturePixelType.L8,
A8 => TexturePixelType.A8,
_ => throw new NotSupportedException("Unsupported pixel type."),
@@ -452,17 +499,35 @@ namespace Robust.Client.Graphics.Clyde
}
}
private static void FlipCopySubRegion<T>(
UIBox2i srcBox,
int w,
ReadOnlySpan<T> srcSpan,
Span<T> copyBuffer)
where T : unmanaged, IPixel<T>
{
var subH = srcBox.Height;
var subW = srcBox.Width;
var dr = subH - 1;
for (var r = 0; r < subH; r++, dr--)
{
var si = r * w + srcBox.Left;
var di = dr * subW;
var srcRow = srcSpan[si..(si + subW)];
var dstRow = copyBuffer[di..(di + subW)];
srcRow.CopyTo(dstRow);
}
}
private static Image<Rgba32> ApplyA8Swizzle(Image<A8> source)
{
var newImage = new Image<Rgba32>(source.Width, source.Height);
var sourceSpan = source.GetPixelSpan();
var destSpan = newImage.GetPixelSpan();
for (var i = 0; i < sourceSpan.Length; i++)
{
var px = sourceSpan[i].PackedValue;
destSpan[i] = new Rgba32(255, 255, 255, px);
}
ApplyA8Swizzle(sourceSpan, destSpan);
return newImage;
}
@@ -473,15 +538,28 @@ namespace Robust.Client.Graphics.Clyde
var sourceSpan = source.GetPixelSpan();
var destSpan = newImage.GetPixelSpan();
for (var i = 0; i < sourceSpan.Length; i++)
{
var px = sourceSpan[i].PackedValue;
destSpan[i] = new Rgba32(px, px, px, 255);
}
ApplyL8Swizzle(sourceSpan, destSpan);
return newImage;
}
private static void ApplyL8Swizzle(ReadOnlySpan<L8> src, Span<Rgba32> dst)
{
for (var i = 0; i < src.Length; i++)
{
var px = src[i].PackedValue;
dst[i] = new Rgba32(px, px, px, 255);
}
}
private static void ApplyA8Swizzle(ReadOnlySpan<A8> src, Span<Rgba32> dst)
{
for (var i = 0; i < src.Length; i++)
{
var px = src[i].PackedValue;
dst[i] = new Rgba32(255, 255, 255, px);
}
}
private sealed class LoadedTexture
{
@@ -525,6 +603,11 @@ namespace Robust.Client.Graphics.Clyde
_clyde.SetSubImage(this, topLeft, sourceImage, sourceRegion);
}
public override void SetSubImage<T>(Vector2i topLeft, Vector2i size, ReadOnlySpan<T> buffer)
{
_clyde.SetSubImage(this, topLeft, size, buffer);
}
protected override void Dispose(bool disposing)
{
if (disposing)

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
@@ -13,7 +12,7 @@ using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using static Robust.Client.Utility.LiterallyJustMessageBox;
using static Robust.Client.Utility.Win32;
using FrameEventArgs = Robust.Shared.Timing.FrameEventArgs;
namespace Robust.Client.Graphics.Clyde
@@ -26,6 +25,7 @@ namespace Robust.Client.Graphics.Clyde
private IWindowingImpl? _windowing;
private Renderer _chosenRenderer;
private ResourcePath? _windowIconPath;
private Thread? _windowingThread;
private bool _vSync;
private WindowMode _windowMode;
@@ -39,7 +39,7 @@ namespace Robust.Client.Graphics.Clyde
public event Action<KeyEventArgs>? KeyUp;
public event Action<KeyEventArgs>? KeyDown;
public event Action<MouseWheelEventArgs>? MouseWheel;
public event Action<WindowClosedEventArgs>? CloseWindow;
public event Action<WindowRequestClosedEventArgs>? CloseWindow;
public event Action<WindowDestroyedEventArgs>? DestroyWindow;
public event Action<WindowContentScaleEventArgs>? OnWindowScaleChanged;
public event Action<WindowResizedEventArgs>? OnWindowResized;
@@ -87,6 +87,10 @@ namespace Robust.Client.Graphics.Clyde
private bool InitWindowing()
{
var iconPath = _cfg.GetCVar(CVars.DisplayWindowIconSet);
if (!string.IsNullOrWhiteSpace(iconPath))
_windowIconPath = new ResourcePath(iconPath);
_windowingThread = Thread.CurrentThread;
_windowing = new GlfwWindowingImpl(this);
@@ -164,13 +168,13 @@ namespace Robust.Client.Graphics.Clyde
private IEnumerable<Image<Rgba32>> LoadWindowIcons()
{
if (OperatingSystem.IsMacOS())
if (OperatingSystem.IsMacOS() || _windowIconPath == null)
{
// Does nothing on macOS so don't bother.
yield break;
}
foreach (var file in _resourceCache.ContentFindFiles("/Textures/Logo/icon"))
foreach (var file in _resourceCache.ContentFindFiles(_windowIconPath))
{
if (file.Extension != "png")
{
@@ -210,11 +214,11 @@ namespace Robust.Client.Graphics.Clyde
_windowing!.WindowRequestAttention(_windowing.MainWindow!);
}
public async Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters)
public IClydeWindow CreateWindow(WindowCreateParameters parameters)
{
DebugTools.AssertNotNull(_windowing);
return await _windowing!.WindowCreate(parameters);
return _windowing!.WindowCreate(parameters);
}
private void DoDestroyWindow(WindowReg reg)
@@ -222,8 +226,11 @@ namespace Robust.Client.Graphics.Clyde
if (reg.IsMainWindow)
throw new InvalidOperationException("Cannot destroy main window.");
_windowing!.WindowDestroy(reg);
reg.BlitDoneEvent?.Set();
_windowing!.WindowDestroy(reg);
var destroyed = new WindowDestroyedEventArgs(reg.Handle);
DestroyWindow?.Invoke(destroyed);
reg.Closed?.Invoke(destroyed);
}
public void ProcessInput(FrameEventArgs frameEventArgs)
@@ -396,10 +403,11 @@ namespace Robust.Client.Graphics.Clyde
public bool IsMainWindow;
public WindowHandle Handle = default!;
public RenderTexture? RenderTexture;
public Action<WindowClosedEventArgs>? Closed;
public Action<WindowRequestClosedEventArgs>? RequestClosed;
public Action<WindowDestroyedEventArgs>? Closed;
}
private sealed class WindowHandle : IClydeWindow
private sealed class WindowHandle : IClydeWindowInternal
{
// So funny story
// When this class was a record, the C# compiler on .NET 5 stack overflowed
@@ -407,65 +415,73 @@ namespace Robust.Client.Graphics.Clyde
// VERY funny.
private readonly Clyde _clyde;
private readonly WindowReg _reg;
public readonly WindowReg Reg;
public bool IsDisposed => _reg.IsDisposed;
public WindowId Id => _reg.Id;
public bool IsDisposed => Reg.IsDisposed;
public WindowId Id => Reg.Id;
public WindowHandle(Clyde clyde, WindowReg reg)
{
_clyde = clyde;
_reg = reg;
Reg = reg;
}
public void Dispose()
{
_clyde.DoDestroyWindow(_reg);
_clyde.DoDestroyWindow(Reg);
}
public Vector2i Size => _reg.FramebufferSize;
public Vector2i Size => Reg.FramebufferSize;
public IRenderTarget RenderTarget
{
get
{
if (_reg.IsMainWindow)
if (Reg.IsMainWindow)
{
return _clyde._mainMainWindowRenderMainTarget;
}
return _reg.RenderTexture!;
return Reg.RenderTexture!;
}
}
public string Title
{
get => _reg.Title;
set => _clyde._windowing!.WindowSetTitle(_reg, value);
get => Reg.Title;
set => _clyde._windowing!.WindowSetTitle(Reg, value);
}
public bool IsFocused => _reg.IsFocused;
public bool IsMinimized => _reg.IsMinimized;
public bool IsFocused => Reg.IsFocused;
public bool IsMinimized => Reg.IsMinimized;
public bool IsVisible
{
get => _reg.IsVisible;
set => _clyde.SetWindowVisible(_reg, value);
get => Reg.IsVisible;
set => _clyde.SetWindowVisible(Reg, value);
}
public Vector2 ContentScale => _reg.WindowScale;
public Vector2 ContentScale => Reg.WindowScale;
public bool DisposeOnClose
{
get => _reg.DisposeOnClose;
set => _reg.DisposeOnClose = value;
get => Reg.DisposeOnClose;
set => Reg.DisposeOnClose = value;
}
public event Action<WindowClosedEventArgs> Closed
public event Action<WindowRequestClosedEventArgs> RequestClosed
{
add => _reg.Closed += value;
remove => _reg.Closed -= value;
add => Reg.RequestClosed += value;
remove => Reg.RequestClosed -= value;
}
public event Action<WindowDestroyedEventArgs>? Destroyed
{
add => Reg.Closed += value;
remove => Reg.Closed -= value;
}
public nint? WindowsHWnd => _clyde._windowing!.WindowGetWin32Window(Reg);
}
private sealed class MonitorHandle : IClydeMonitor

View File

@@ -24,6 +24,7 @@ namespace Robust.Client.Graphics.Clyde
internal sealed partial class Clyde : IClydeInternal, IClydeAudio, IPostInjectInit
{
[Dependency] private readonly IClydeTileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly ILightManager _lightManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;

View File

@@ -54,7 +54,7 @@ namespace Robust.Client.Graphics.Clyde
public event Action<KeyEventArgs>? KeyUp { add { } remove { } }
public event Action<KeyEventArgs>? KeyDown { add { } remove { } }
public event Action<MouseWheelEventArgs>? MouseWheel { add { } remove { } }
public event Action<WindowClosedEventArgs>? CloseWindow { add { } remove { } }
public event Action<WindowRequestClosedEventArgs>? CloseWindow { add { } remove { } }
public event Action<WindowDestroyedEventArgs>? DestroyWindow { add { } remove { } }
public Texture GetStockTexture(ClydeStockTexture stockTexture)
@@ -210,7 +210,7 @@ namespace Robust.Client.Graphics.Clyde
yield break;
}
public Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters)
public IClydeWindow CreateWindow(WindowCreateParameters parameters)
{
var window = new DummyWindow(CreateRenderTarget((123, 123), default))
{
@@ -218,7 +218,7 @@ namespace Robust.Client.Graphics.Clyde
};
_windows.Add(window);
return Task.FromResult<IClydeWindow>(window);
return window;
}
public ClydeHandle LoadShader(ParsedShader shader, string? name = null)
@@ -396,6 +396,11 @@ namespace Robust.Client.Graphics.Clyde
{
// Just do nothing on mutate.
}
public override void SetSubImage<T>(Vector2i topLeft, Vector2i size, ReadOnlySpan<T> buffer)
{
// Just do nothing on mutate.
}
}
private sealed class DummyShaderInstance : ShaderInstance
@@ -605,7 +610,8 @@ namespace Robust.Client.Graphics.Clyde
public bool IsVisible { get; set; } = true;
public Vector2 ContentScale => Vector2.One;
public bool DisposeOnClose { get; set; }
public event Action<WindowClosedEventArgs>? Closed { add { } remove { } }
public event Action<WindowRequestClosedEventArgs>? RequestClosed { add { } remove { } }
public event Action<WindowDestroyedEventArgs>? Destroyed;
public void MaximizeOnMonitor(IClydeMonitor monitor)
{
@@ -614,6 +620,8 @@ namespace Robust.Client.Graphics.Clyde
public void Dispose()
{
IsDisposed = true;
Destroyed?.Invoke(new WindowDestroyedEventArgs(this));
}
}
}

View File

@@ -131,10 +131,10 @@ namespace Robust.Client.Graphics.Clyde
private void ProcessEventKey(EventKey ev)
{
EmitKeyEvent(ConvertGlfwKey(ev.Key), ev.Action, ev.Mods);
EmitKeyEvent(ConvertGlfwKey(ev.Key), ev.Action, ev.Mods, ev.ScanCode);
}
private void EmitKeyEvent(Keyboard.Key key, InputAction action, KeyModifiers mods)
private void EmitKeyEvent(Keyboard.Key key, InputAction action, KeyModifiers mods, int scanCode)
{
var shift = (mods & KeyModifiers.Shift) != 0;
var alt = (mods & KeyModifiers.Alt) != 0;
@@ -144,7 +144,8 @@ namespace Robust.Client.Graphics.Clyde
var ev = new KeyEventArgs(
key,
action == InputAction.Repeat,
alt, control, shift, system);
alt, control, shift, system,
scanCode);
switch (action)
{
@@ -162,7 +163,7 @@ namespace Robust.Client.Graphics.Clyde
private void ProcessEventMouseButton(EventMouseButton ev)
{
EmitKeyEvent(Mouse.MouseButtonToKey(ConvertGlfwButton(ev.Button)), ev.Action, ev.Mods);
EmitKeyEvent(Mouse.MouseButtonToKey(ConvertGlfwButton(ev.Button)), ev.Action, ev.Mods, default);
}
private void ProcessEventScroll(EventScroll ev)
@@ -183,7 +184,7 @@ namespace Robust.Client.Graphics.Clyde
if (windowReg == null)
return;
_clyde.SendCloseWindow(windowReg, new WindowClosedEventArgs(windowReg.Handle));
_clyde.SendCloseWindow(windowReg, new WindowRequestClosedEventArgs(windowReg.Handle));
}
private void ProcessEventWindowSize(EventWindowSize ev)

View File

@@ -207,6 +207,7 @@ namespace Robust.Client.Graphics.Clyde
Renderer Renderer,
WindowCreateParameters Parameters,
nint ShareWindow,
nint OwnerWindow,
TaskCompletionSource<GlfwWindowCreateResult> Tcs
) : CmdBase;

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Threading.Tasks;
using OpenToolkit;
using OpenToolkit.Graphics.OpenGL4;
@@ -22,8 +21,7 @@ namespace Robust.Client.Graphics.Clyde
{
internal partial class Clyde
{
// Wait for it.
private sealed partial class GlfwWindowingImpl
private unsafe sealed partial class GlfwWindowingImpl
{
private readonly List<GlfwWindowReg> _windows = new();
@@ -36,45 +34,31 @@ namespace Robust.Client.Graphics.Clyde
private int _nextWindowId = 1;
private static bool _eglLoaded;
public async Task<WindowHandle> WindowCreate(WindowCreateParameters parameters)
public WindowHandle WindowCreate(WindowCreateParameters parameters)
{
// tfw await not allowed in unsafe contexts
// GL APIs don't take kindly to making a new window without unbinding the main context. Great.
// Leaving code for async path in, in case it works on like GLX.
var unbindContextAndBlock = true;
DebugTools.AssertNotNull(_mainWindow);
Task<GlfwWindowCreateResult> task;
unsafe
{
if (unbindContextAndBlock)
GLFW.MakeContextCurrent(null);
GLFW.MakeContextCurrent(null);
task = SharedWindowCreate(
_clyde._chosenRenderer,
parameters,
_mainWindow!.GlfwWindow);
Window* ownerPtr = null;
if (parameters.Owner != null)
{
var ownerReg = (GlfwWindowReg) ((WindowHandle)parameters.Owner).Reg;
ownerPtr = ownerReg.GlfwWindow;
}
if (unbindContextAndBlock)
{
unsafe
{
// Block the main thread (to avoid stuff like texture uploads being problematic).
WaitWindowCreate(task);
var task = SharedWindowCreate(
_clyde._chosenRenderer,
parameters,
_mainWindow!.GlfwWindow,
ownerPtr);
if (unbindContextAndBlock)
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
}
}
else
{
await task;
}
// Block the main thread (to avoid stuff like texture uploads being problematic).
WaitWindowCreate(task);
var (reg, error) = await task;
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
var (reg, error) = task.Result;
if (reg == null)
{
@@ -85,18 +69,11 @@ namespace Robust.Client.Graphics.Clyde
_clyde.CreateWindowRenderTexture(reg);
_clyde.InitWindowBlitThread(reg);
unsafe
{
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
}
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
return reg.Handle;
}
}
// Yes, you read that right.
private sealed unsafe partial class GlfwWindowingImpl
{
public bool TryInitMainWindow(Renderer renderer, [NotNullWhen(false)] out string? error)
{
var width = _cfg.GetCVar(CVars.DisplayWidth);
@@ -123,7 +100,7 @@ namespace Robust.Client.Graphics.Clyde
Fullscreen = fullscreen
};
var windowTask = SharedWindowCreate(renderer, parameters, null);
var windowTask = SharedWindowCreate(renderer, parameters, null, null);
WaitWindowCreate(windowTask);
var (reg, err) = windowTask.Result;
@@ -165,14 +142,30 @@ namespace Robust.Client.Graphics.Clyde
private Task<GlfwWindowCreateResult> SharedWindowCreate(
Renderer renderer,
WindowCreateParameters parameters,
Window* share)
Window* share, Window* owner)
{
//
// IF YOU'RE WONDERING WHY THIS IS TASK-BASED:
// I originally wanted this to be async so we could avoid blocking the main thread
// while the OS takes its stupid 100~ms just to initialize a fucking GL context.
// This doesn't *work* because
// we have to release the GL context while the shared context is being created.
// (at least on WGL, I didn't test other platforms and I don't care to.)
// Not worth it to avoid a main thread blockage by allowing Clyde to temporarily release the GL context,
// because rendering would be locked up *anyways*.
//
// Basically what I'm saying is that everything about OpenGL is a fucking mistake
// and I should get on either Veldrid or Vulkan some time.
// Probably Veldrid tbh.
//
// Yes we ping-pong this TCS through the window thread and back, deal with it.
var tcs = new TaskCompletionSource<GlfwWindowCreateResult>();
SendCmd(new CmdWinCreate(
renderer,
parameters,
(nint) share,
(nint) owner,
tcs));
return tcs.Task;
@@ -194,9 +187,9 @@ namespace Robust.Client.Graphics.Clyde
private void WinThreadWinCreate(CmdWinCreate cmd)
{
var (renderer, parameters, share, tcs) = cmd;
var (renderer, parameters, share, owner, tcs) = cmd;
var window = CreateGlfwWindowForRenderer(renderer, parameters, (Window*) share);
var window = CreateGlfwWindowForRenderer(renderer, parameters, (Window*) share, (Window*) owner);
if (window == null)
{
@@ -418,6 +411,22 @@ namespace Robust.Client.Graphics.Clyde
}
}
public nint? WindowGetWin32Window(WindowReg window)
{
if (!OperatingSystem.IsWindows())
return null;
var reg = (GlfwWindowReg) window;
try
{
return GLFW.GetWin32Window(reg.GlfwWindow);
}
catch (EntryPointNotFoundException)
{
return null;
}
}
public void WindowDestroy(WindowReg window)
{
var reg = (GlfwWindowReg) window;
@@ -430,14 +439,13 @@ namespace Robust.Client.Graphics.Clyde
_windows.Remove(reg);
_clyde._windowHandles.Remove(reg.Handle);
_clyde.DestroyWindow?.Invoke(new WindowDestroyedEventArgs(window.Handle));
}
private Window* CreateGlfwWindowForRenderer(
Renderer r,
WindowCreateParameters parameters,
Window* contextShare)
Window* contextShare,
Window* ownerWindow)
{
#if DEBUG
GLFW.WindowHint(WindowHintBool.OpenGLDebugContext, true);
@@ -445,6 +453,8 @@ namespace Robust.Client.Graphics.Clyde
GLFW.WindowHint(WindowHintString.X11ClassName, "SS14");
GLFW.WindowHint(WindowHintString.X11InstanceName, "SS14");
GLFW.WindowHint(WindowHintBool.ScaleToMonitor, true);
if (r == Renderer.OpenGL33)
{
GLFW.WindowHint(WindowHintInt.ContextVersionMajor, 3);
@@ -519,13 +529,63 @@ namespace Robust.Client.Graphics.Clyde
GLFW.MaximizeWindow(window);
}
if ((parameters.Styles & OSWindowStyles.NoTitleOptions) != 0)
{
if (OperatingSystem.IsWindows())
{
var hWnd = (void*) GLFW.GetWin32Window(window);
DebugTools.Assert(hWnd != null);
Win32.SetWindowLongPtrW(
hWnd,
Win32.GWL_STYLE,
// Cast to long here to work around a bug in rider with nint bitwise operators.
(nint)((long)Win32.GetWindowLongPtrW(hWnd, Win32.GWL_STYLE) & ~Win32.WS_SYSMENU));
}
else
{
_sawmill.Warning("OSWindowStyles.NoTitleOptions not implemented on this platform");
}
}
if (ownerWindow != null)
{
if (OperatingSystem.IsWindows())
{
var hWnd = (void*) GLFW.GetWin32Window(window);
var ownerHWnd = GLFW.GetWin32Window(ownerWindow);
DebugTools.Assert(hWnd != null);
Win32.SetWindowLongPtrW(
hWnd,
Win32.GWLP_HWNDPARENT,
ownerHWnd);
}
else
{
_sawmill.Warning("owner windows not implemented on this platform");
}
if (parameters.StartupLocation == WindowStartupLocation.CenterOwner)
{
// TODO: Maybe include window frames in size calculations here?
// Figure out frame sizes of both windows.
GLFW.GetWindowPos(ownerWindow, out var ownerX, out var ownerY);
GLFW.GetWindowSize(ownerWindow, out var ownerW, out var ownerH);
// Re-fetch this in case DPI scaling is changing it I guess.
GLFW.GetWindowSize(window, out var thisW, out var thisH);
GLFW.SetWindowPos(window, ownerX + (ownerW - thisW) / 2, ownerY + (ownerH - thisH) / 2);
}
}
if (parameters.Visible)
{
GLFW.ShowWindow(window);
}
return window;
}

View File

@@ -38,7 +38,8 @@ namespace Robust.Client.Graphics.Clyde
void WindowRequestAttention(WindowReg window);
void WindowSwapBuffers(WindowReg window);
uint? WindowGetX11Id(WindowReg window);
Task<WindowHandle> WindowCreate(WindowCreateParameters parameters);
nint? WindowGetWin32Window(WindowReg window);
WindowHandle WindowCreate(WindowCreateParameters parameters);
void WindowDestroy(WindowReg reg);
string KeyGetName(Keyboard.Key key);

View File

@@ -130,6 +130,6 @@ namespace Robust.Client.Graphics
IEnumerable<IClydeMonitor> EnumerateMonitors();
Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters);
IClydeWindow CreateWindow(WindowCreateParameters parameters);
}
}

View File

@@ -28,7 +28,7 @@ namespace Robust.Client.Graphics
event Action<KeyEventArgs> KeyUp;
event Action<KeyEventArgs> KeyDown;
event Action<MouseWheelEventArgs> MouseWheel;
event Action<WindowClosedEventArgs> CloseWindow;
event Action<WindowRequestClosedEventArgs> CloseWindow;
event Action<WindowDestroyedEventArgs> DestroyWindow;
ClydeHandle LoadShader(ParsedShader shader, string? name = null);

View File

@@ -28,6 +28,17 @@ namespace Robust.Client.Graphics
/// Fired when the user tries to close the window. Note that if <see cref="DisposeOnClose"/> is not true,
/// this is merely a request and the user pressing the close button does nothing.
/// </summary>
event Action<WindowClosedEventArgs> Closed;
event Action<WindowRequestClosedEventArgs> RequestClosed;
/// <summary>
/// Raised when the window has been definitively closed.
/// This means the window must not be used anymore (it is disposed).
/// </summary>
event Action<WindowDestroyedEventArgs> Destroyed;
}
public interface IClydeWindowInternal : IClydeWindow
{
nint? WindowsHWnd { get; }
}
}

View File

@@ -42,6 +42,9 @@ namespace Robust.Client.Graphics
SetSubImage(topLeft, sourceImage, UIBox2i.FromDimensions(0, 0, sourceImage.Width, sourceImage.Height));
}
public abstract void SetSubImage<T>(Vector2i topLeft, Vector2i size, ReadOnlySpan<T> buffer)
where T : unmanaged, IPixel<T>;
public void Dispose()
{
Dispose(true);

View File

@@ -1,14 +0,0 @@
using System;
namespace Robust.Client.Graphics
{
public class WindowClosedEventArgs : EventArgs
{
public IClydeWindow Window { get; }
public WindowClosedEventArgs(IClydeWindow window)
{
Window = window;
}
}
}

View File

@@ -1,8 +1,6 @@
using System;
namespace Robust.Client.Graphics
namespace Robust.Client.Graphics
{
public sealed class WindowContentScaleEventArgs : EventArgs
public readonly struct WindowContentScaleEventArgs
{
public WindowContentScaleEventArgs(IClydeWindow window)
{

View File

@@ -1,4 +1,6 @@
namespace Robust.Client.Graphics
using System;
namespace Robust.Client.Graphics
{
public sealed class WindowCreateParameters
{
@@ -9,5 +11,54 @@
public bool Visible = true;
public IClydeMonitor? Monitor;
public bool Fullscreen;
/// <summary>
/// The window that will "own" this window.
/// Owned windows always appear on top of their owners and have some other misc behavior depending on the OS.
/// </summary>
public IClydeWindow? Owner;
/// <summary>
/// Controls where a window is initially placed when created.
/// </summary>
public WindowStartupLocation StartupLocation;
/// <summary>
/// Specifies window styling options for the created window.
/// </summary>
public OSWindowStyles Styles;
}
/// <summary>
/// Controls where a window is initially placed when created.
/// </summary>
public enum WindowStartupLocation : byte
{
/// <summary>
/// The window position is automatically picked by the windowing system.
/// </summary>
Manual,
/// <summary>
/// The window is positioned at the center of the <see cref="WindowCreateParameters.Owner"/> window.
/// </summary>
CenterOwner,
}
/// <summary>
/// Specifies window styling options for an OS window.
/// </summary>
[Flags]
public enum OSWindowStyles
{
/// <summary>
/// No special styles set.
/// </summary>
None = 0,
/// <summary>
/// Hide title buttons such as close and minimize.
/// </summary>
NoTitleOptions = 1 << 0
}
}

View File

@@ -1,8 +1,6 @@
using System;
namespace Robust.Client.Graphics
namespace Robust.Client.Graphics
{
public class WindowDestroyedEventArgs : EventArgs
public readonly struct WindowDestroyedEventArgs
{
public IClydeWindow Window { get; }

View File

@@ -1,8 +1,6 @@
using System;
namespace Robust.Client.Graphics
namespace Robust.Client.Graphics
{
public class WindowFocusedEventArgs : EventArgs
public readonly struct WindowFocusedEventArgs
{
public WindowFocusedEventArgs(bool focused, IClydeWindow window)
{

View File

@@ -0,0 +1,12 @@
namespace Robust.Client.Graphics
{
public readonly struct WindowRequestClosedEventArgs
{
public IClydeWindow Window { get; }
public WindowRequestClosedEventArgs(IClydeWindow window)
{
Window = window;
}
}
}

View File

@@ -1,9 +1,8 @@
using System;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics
{
public class WindowResizedEventArgs : EventArgs
public readonly struct WindowResizedEventArgs
{
public WindowResizedEventArgs(Vector2i oldSize, Vector2i newSize, IClydeWindow window)
{

View File

@@ -76,11 +76,18 @@ namespace Robust.Client.Input
/// </summary>
public bool IsRepeat { get; }
public KeyEventArgs(Keyboard.Key key, bool repeat, bool alt, bool control, bool shift, bool system)
public int ScanCode { get; }
public KeyEventArgs(
Keyboard.Key key,
bool repeat,
bool alt, bool control, bool shift, bool system,
int scanCode)
: base(alt, control, shift, system)
{
Key = key;
IsRepeat = repeat;
ScanCode = scanCode;
}
}

View File

@@ -129,5 +129,7 @@ namespace Robust.Client.Input
void ResetAllBindings();
bool IsKeyFunctionModified(BoundKeyFunction function);
bool IsKeyDown(Keyboard.Key key);
}
}

View File

@@ -16,6 +16,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
@@ -41,7 +42,7 @@ namespace Robust.Client.Input
[Dependency] private readonly IResourceManager _resourceMan = default!;
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IUserInterfaceManagerInternal _userInterfaceManagerInternal = default!;
[Dependency] private readonly IUserInterfaceManagerInternal _uiMgr = default!;
private bool _currentlyFindingViewport;
@@ -248,7 +249,15 @@ namespace Robust.Client.Input
var uiOnly = false;
if (hasCanFocus)
{
uiOnly = _userInterfaceManagerInternal.HandleCanFocusDown(MouseScreenPosition);
uiOnly = _uiMgr.HandleCanFocusDown(MouseScreenPosition, out _);
}
if (_uiMgr.KeyboardFocused is IRawInputControl rawInput)
{
var block = RaiseRawKeyInput(args, rawInput, args.IsRepeat ? RawKeyAction.Repeat : RawKeyAction.Down);
if (block)
return;
}
foreach (var binding in bindsDown)
@@ -260,6 +269,21 @@ namespace Robust.Client.Input
}
}
private bool RaiseRawKeyInput(KeyEventArgs args, IRawInputControl rawInput, RawKeyAction action)
{
DebugTools.AssertNotNull(_uiMgr.KeyboardFocused);
var mousePos = _uiMgr.CalcRelativeMousePositionFor(_uiMgr.KeyboardFocused!, _uiMgr.MousePositionScaled);
var keyEvent = new GuiRawKeyEvent(
args.Key,
args.ScanCode,
action,
(Vector2i) (mousePos ?? Vector2.Zero));
var block = rawInput.RawKeyEvent(keyEvent);
return block;
}
/// <inheritdoc />
public void KeyUp(KeyEventArgs args)
{
@@ -289,8 +313,11 @@ namespace Robust.Client.Input
if (hasCanFocus)
{
_userInterfaceManagerInternal.HandleCanFocusUp();
_uiMgr.HandleCanFocusUp();
}
if (_uiMgr.KeyboardFocused is IRawInputControl rawInput)
RaiseRawKeyInput(args, rawInput, RawKeyAction.Up);
}
private bool DownBind(KeyBinding binding, bool uiOnly, bool isRepeat)
@@ -607,6 +634,11 @@ namespace Robust.Client.Input
return _modifiedKeyFunctions.Contains(function);
}
public bool IsKeyDown(Key key)
{
return _keysPressed[(int) key];
}
/// <inheritdoc />
public bool TryGetKeyBinding(BoundKeyFunction function, [NotNullWhen(true)] out IKeyBinding? binding)
{

View File

@@ -20,6 +20,9 @@ namespace Robust.Client.Log
public void Log(string sawmillName, LogEvent message)
{
if (sawmillName == "CON")
return;
var formatted = new FormattedMessage(8);
var robustLevel = message.Level.ToRobust();
formatted.PushColor(Color.DarkGray);

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
@@ -134,15 +135,24 @@ namespace Robust.Client.Map
}
}
}
chunk.SuppressCollisionRegeneration = false;
chunk.RegenerateCollision();
}
if (modified.Count != 0)
{
InvokeGridChanged(this, new GridChangedEventArgs(grid, modified));
}
foreach (var chunkData in gridDatum.ChunkData)
{
var chunk = grid.GetChunk(chunkData.Index);
chunk.SuppressCollisionRegeneration = false;
chunk.RegenerateCollision();
}
foreach (var chunkData in gridDatum.DeletedChunkData)
{
grid.RemoveChunk(chunkData.Index);
}
}
SuppressOnTileChanged = false;

View File

@@ -8,6 +8,7 @@ using Robust.Client.Utility;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -62,7 +63,7 @@ namespace Robust.Client.Map
var row = i / dimensionX;
Image<Rgba32> image;
using (var stream = _resourceCache.ContentFileRead(Path.Join(def.Path, $"{def.SpriteName}.png")))
using (var stream = _resourceCache.ContentFileRead(new ResourcePath(def.Path) / $"{def.SpriteName}.png"))
{
image = Image.Load<Rgba32>(stream);
}

View File

@@ -1,8 +1,8 @@
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Physics;
namespace Robust.Client.Physics
{
internal sealed class BroadPhaseSystem : SharedBroadPhaseSystem
internal sealed class BroadPhaseSystem : SharedBroadphaseSystem
{
public override void Initialize()
{

View File

@@ -0,0 +1,57 @@
using System.Collections.Generic;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Physics.Dynamics;
namespace Robust.Client.Physics
{
[RegisterComponent]
[ComponentReference(typeof(SharedPhysicsMapComponent))]
public sealed class PhysicsMapComponent : SharedPhysicsMapComponent
{
private float _timeToSleep;
protected override void Initialize()
{
base.Initialize();
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.OnValueChanged(CVars.TimeToSleep, SetTimeToSleep);
}
protected override void OnRemove()
{
base.OnRemove();
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.UnsubValueChanged(CVars.TimeToSleep, SetTimeToSleep);
}
private void SetTimeToSleep(float value) => _timeToSleep = value;
protected override void Cleanup(float frameTime)
{
var toRemove = new List<PhysicsComponent>();
// Because we're not predicting 99% of bodies its sleep timer never gets incremented so we'll just do it ourselves.
// (and serializing it over the network isn't necessary?)
// This is a client-only problem.
foreach (var body in AwakeBodies)
{
if (body.Island || body.LinearVelocity.Length > 0f || body.AngularVelocity != 0f) continue;
body.SleepTime += frameTime;
if (body.SleepTime > _timeToSleep)
{
toRemove.Add(body);
}
}
foreach (var body in toRemove)
{
body.Awake = false;
}
base.Cleanup(frameTime);
}
}
}

View File

@@ -2,6 +2,7 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Timing;
namespace Robust.Client.Physics
@@ -16,8 +17,7 @@ namespace Robust.Client.Physics
public override void Update(float frameTime)
{
_lastRem = _gameTiming.CurTime;
SimulateWorld(frameTime, !_gameTiming.InSimulation || !_gameTiming.IsFirstTimePredicted);
SimulateWorld(frameTime, _gameTiming.InPrediction);
}
public override void FrameUpdate(float frameTime)
@@ -31,5 +31,11 @@ namespace Robust.Client.Physics
_lastRem = _gameTiming.TickRemainder;
SimulateWorld((float) diff.TotalSeconds, true);
}
protected override void HandleMapCreated(object? sender, MapEventArgs eventArgs)
{
if (eventArgs.Map == MapId.Nullspace) return;
MapManager.GetMapEntity(eventArgs.Map).AddComponent<PhysicsMapComponent>();
}
}
}

View File

@@ -11,7 +11,8 @@ namespace Robust.Client.Placement.Modes
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
{
const float SearchBoxSize = 1.5f; // size of search box in meters
// Go over diagonal size so when placing in a line it doesn't stop snapping.
const float SearchBoxSize = 2f; // size of search box in meters
MouseCoords = ScreenToCursorGrid(mouseScreen).AlignWithClosestGridTile(SearchBoxSize, pManager.EntityManager, pManager.MapManager);

View File

@@ -504,8 +504,8 @@ namespace Robust.Client.Placement
coordinates = new EntityCoordinates();
return false;
}
coordinates = EntityCoordinates.FromMap(ent.EntityManager, MapManager,
eyeManager.ScreenToMap(_inputManager.MouseScreenPosition));
coordinates = EntityCoordinates.FromMap(MapManager,
eyeManager.ScreenToMap(_inputManager.MouseScreenPosition));
return true;
}
}

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