Compare commits

...

77 Commits

Author SHA1 Message Date
Acruid
ef22842b90 Fixes bug where FirstTimePredicted was not being set properly for the first predicted frame. 2021-03-27 20:16:04 -07:00
Pieter-Jan Briers
303e2152d2 UIScale now updates dynamically.
So if you move the window between different monitors with different scaling, the game updates.
2021-03-28 01:55:35 +01:00
Vera Aguilera Puerto
37fc0d0d2a Set correct class constrains for prototype id list serializers 2021-03-27 22:47:24 +01:00
Vera Aguilera Puerto
53987e1e5d Adds prototype "Variant" helper methods to IPrototypeManager (#1662) 2021-03-27 22:40:07 +01:00
metalgearsloth
3216d7770b Fix net.rate cvar warning (#1659)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-27 02:14:45 -07:00
Acruid
3203ca2ff4 Removed Control.Update from the UI system. UI Controls have no business running code in simulation updates.
Refactored the client update loop so that the GameStateManager is in full control of the simulation update.
2021-03-26 17:46:34 -07:00
metalgearsloth
e22254cd51 Clear velocities on container insertion (#1653)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-26 22:39:11 +01:00
Acruid
7ed722f669 Visibility moved from session to EyeComponent (#1657) 2021-03-26 22:38:45 +01:00
DrSmugleaf
4864096b2a Add prototype id list serializer and tests (#1658)
* Add prototype id list serializer and tests

* Bring old .Value code back

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

* Paul changes

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

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

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

* Slight tweak

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

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

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

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

* Added entity create/delete events to IEntityManager.

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

* Component events are now just overloads of entity events.

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

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

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

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

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

* Organization

* Still doesnt work

* Formatting

* It works!!

* More changes to everything

* Beginning of changes to overlays

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

* Stencils are easy

* Questionable changes to overlays

* Minor change to HLR

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

* Fixes misleading message

* Adds a variety of worldspaces for overlays to choose from

* Caching

* Address reviews

* Merging pains

* ah.

* ahhhhh

* minor overlaymanager changes

* Work

* fix

* Merge??

* Fixes null errors

* Force update

* Delete whatever the fuck this is?

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

View File

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

View File

@@ -11,7 +11,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Utility;
@@ -321,6 +320,11 @@ namespace Robust.Client.Audio.Midi
continue;
}
if (renderer.TrackingEntity != null)
{
renderer.Source.SetVelocity(renderer.TrackingEntity.GlobalLinearVelocity());
}
if (float.IsNaN(pos.Position.X) || float.IsNaN(pos.Position.Y))
{
// just duck out instead of move to NaN

View File

@@ -27,8 +27,6 @@ using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
namespace Robust.Client
{
@@ -66,8 +64,6 @@ namespace Robust.Client
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IFontManager, FontManager>();
IoCManager.Register<IFontManagerInternal, FontManager>();
IoCManager.Register<IMidiManager, MidiManager>();
IoCManager.Register<IAuthManager, AuthManager>();
switch (mode)
@@ -94,8 +90,9 @@ namespace Robust.Client
throw new ArgumentOutOfRangeException();
}
IoCManager.Register<IFontManager, FontManager>();
IoCManager.Register<IFontManagerInternal, FontManager>();
IoCManager.Register<IEyeManager, EyeManager>();
IoCManager.Register<IPlacementManager, PlacementManager>();
IoCManager.Register<IOverlayManager, OverlayManager>();
IoCManager.Register<IOverlayManagerInternal, OverlayManager>();

View File

@@ -1,8 +1,9 @@
using System;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
@@ -41,14 +42,14 @@ namespace Robust.Client.Debugging
_debugColliders = value;
if (value)
if (value && !_overlayManager.HasOverlay<PhysicsOverlay>())
{
_overlayManager.AddOverlay(new PhysicsOverlay(_componentManager, _eyeManager,
_prototypeManager, _inputManager, _physicsManager));
}
else
{
_overlayManager.RemoveOverlay(nameof(PhysicsOverlay));
_overlayManager.RemoveOverlay<PhysicsOverlay>();
}
}
}
@@ -66,13 +67,13 @@ namespace Robust.Client.Debugging
_debugPositions = value;
if (value)
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
{
_overlayManager.AddOverlay(new EntityPositionOverlay(_entityManager, _eyeManager));
}
else
{
_overlayManager.RemoveOverlay(nameof(EntityPositionOverlay));
_overlayManager.RemoveOverlay<EntityPositionOverlay>();
}
}
}
@@ -91,8 +92,8 @@ namespace Robust.Client.Debugging
private Vector2 _hoverStartScreen = Vector2.Zero;
private List<IPhysBody> _hoverBodies = new();
public PhysicsOverlay(IComponentManager compMan, IEyeManager eyeMan, IPrototypeManager protoMan, IInputManager inputManager, IPhysicsManager physicsManager)
: base(nameof(PhysicsOverlay))
{
_componentManager = compMan;
_eyeManager = eyeMan;
@@ -160,6 +161,8 @@ 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);
foreach (var physBody in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(mapId, viewport))
{
@@ -169,9 +172,6 @@ namespace Robust.Client.Debugging
var worldBox = physBody.GetWorldAABB();
if (worldBox.IsEmpty()) continue;
var colorEdge = Color.Red.WithAlpha(0.33f);
var sleepThreshold = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.TimeToSleep);
foreach (var fixture in physBody.Fixtures)
{
var shape = fixture.Shape;
@@ -193,9 +193,9 @@ namespace Robust.Client.Debugging
{
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
foreach (var chr in str)
foreach (var rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, chr, baseLine, 1, Color.White);
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
baseLine += new Vector2(advance, 0);
}
}
@@ -265,7 +265,7 @@ namespace Robust.Client.Debugging
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager) : base(nameof(EntityPositionOverlay))
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager)
{
_entityManager = entityManager;
_eyeManager = eyeManager;

View File

@@ -1,10 +1,11 @@
using Robust.Shared.IoC;
using Robust.Shared.IoC;
using Robust.Shared.Network.Messages;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Enums;
using Robust.Shared.Network;
namespace Robust.Client.Debugging
@@ -38,13 +39,13 @@ namespace Robust.Client.Debugging
_debugDrawRays = value;
if (value)
if (value && !_overlayManager.HasOverlay<DebugDrawRayOverlay>())
{
_overlayManager.AddOverlay(new DebugDrawRayOverlay(this));
}
else
{
_overlayManager.RemoveOverlay(nameof(DebugDrawRayOverlay));
_overlayManager.RemoveOverlay<DebugDrawRayOverlay>();
}
}
}
@@ -81,7 +82,7 @@ namespace Robust.Client.Debugging
private readonly DebugDrawingManager _owner;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DebugDrawRayOverlay(DebugDrawingManager owner) : base(nameof(DebugDrawRayOverlay))
public DebugDrawRayOverlay(DebugDrawingManager owner)
{
_owner = owner;
}

View File

@@ -24,6 +24,7 @@
using System;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -55,7 +56,7 @@ namespace Robust.Client.Debugging
IoCManager.Resolve<IOverlayManager>().AddOverlay(new PhysicsDebugOverlay(this));
if (value == PhysicsDebugFlags.None)
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(nameof(PhysicsDebugOverlay));
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsDebugOverlay));
_flags = value;
}
@@ -119,7 +120,7 @@ namespace Robust.Client.Debugging
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public PhysicsDebugOverlay(DebugPhysicsSystem system) : base(nameof(PhysicsDebugOverlay))
public PhysicsDebugOverlay(DebugPhysicsSystem system)
{
_physics = system;
}

View File

@@ -1,5 +1,6 @@
using System;
using System;
using System.IO;
using System.Management;
using System.Net;
using System.Threading.Tasks;
using Robust.Client.Audio.Midi;
@@ -80,6 +81,74 @@ namespace Robust.Client
}
public bool Startup(Func<ILogHandler>? logHandlerFactory = null)
{
if (!StartupSystemSplash(logHandlerFactory))
return false;
// Disable load context usage on content start.
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
_modLoader.SetUseLoadContext(!_disableAssemblyLoadContext);
_modLoader.SetEnableSandboxing(true);
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), "Content."))
{
Logger.Fatal("Errors while loading content assemblies.");
return false;
}
foreach (var loadedModule in _modLoader.LoadedModules)
{
_configurationManager.LoadCVarsFromAssembly(loadedModule);
}
IoCManager.Resolve<ISerializationManager>().Initialize();
// Call Init in game assemblies.
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
_resourceCache.PreloadTextures();
_userInterfaceManager.Initialize();
_networkManager.Initialize(false);
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
_serializer.Initialize();
_inputManager.Initialize();
_console.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
_prototypeManager.Resync();
_mapManager.Initialize();
_entityManager.Initialize();
_gameStateManager.Initialize();
_placementManager.Initialize();
_viewVariablesManager.Initialize();
_scriptClient.Initialize();
_client.Initialize();
_discord.Initialize();
_modLoader.BroadcastRunLevel(ModRunLevel.PostInit);
if (_commandLineArgs?.Username != null)
{
_client.PlayerNameOverride = _commandLineArgs.Username;
}
_authManager.LoadFromEnv();
GC.Collect();
_clyde.Ready();
if ((_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)
&& LaunchState.ConnectEndpoint != null)
{
_client.ConnectToServer(LaunchState.ConnectEndpoint);
}
return true;
}
private bool StartupSystemSplash(Func<ILogHandler>? logHandlerFactory)
{
ReadInitialLaunchState();
@@ -119,6 +188,8 @@ namespace Robust.Client
_configurationManager.OverrideConVars(_commandLineArgs.CVars);
}
ProfileOptSetup.Setup(_configurationManager);
_resourceCache.Initialize(LoadConfigAndUserData ? userDataDir : null);
ProgramShared.DoMounts(_resourceCache, _commandLineArgs?.MountOptions, "Content.Client", _loaderArgs != null);
@@ -129,6 +200,13 @@ namespace Robust.Client
_modLoader.VerifierExtraLoadHandler = VerifierExtraLoadHandler;
}
_clyde.TextEntered += TextEntered;
_clyde.MouseMove += MouseMove;
_clyde.KeyUp += KeyUp;
_clyde.KeyDown += KeyDown;
_clyde.MouseWheel += MouseWheel;
_clyde.CloseWindow += Shutdown;
// Bring display up as soon as resources are mounted.
if (!_clyde.Initialize())
{
@@ -137,65 +215,7 @@ namespace Robust.Client
_clyde.SetWindowTitle("Space Station 14");
_fontManager.Initialize();
// Disable load context usage on content start.
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
_modLoader.SetUseLoadContext(!_disableAssemblyLoadContext);
_modLoader.SetEnableSandboxing(true);
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), "Content."))
{
Logger.Fatal("Errors while loading content assemblies.");
return false;
}
foreach (var loadedModule in _modLoader.LoadedModules)
{
_configurationManager.LoadCVarsFromAssembly(loadedModule);
}
IoCManager.Resolve<ISerializationManager>().Initialize();
// Call Init in game assemblies.
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
_userInterfaceManager.Initialize();
_networkManager.Initialize(false);
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
_serializer.Initialize();
_inputManager.Initialize();
_console.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
_prototypeManager.Resync();
_mapManager.Initialize();
_entityManager.Initialize();
_gameStateManager.Initialize();
_placementManager.Initialize();
_viewVariablesManager.Initialize();
_scriptClient.Initialize();
_client.Initialize();
_discord.Initialize();
_modLoader.BroadcastRunLevel(ModRunLevel.PostInit);
if (_commandLineArgs?.Username != null)
{
_client.PlayerNameOverride = _commandLineArgs.Username;
}
_authManager.LoadFromEnv();
_clyde.Ready();
if ((_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)
&& LaunchState.ConnectEndpoint != null)
{
_client.ConnectToServer(LaunchState.ConnectEndpoint);
}
_fontManager.SetFontDpi((uint) _configurationManager.GetCVar(CVars.DisplayFontDpi));
return true;
}
@@ -272,17 +292,13 @@ namespace Robust.Client
_modLoader.BroadcastUpdate(ModUpdateLevel.PreEngine, frameEventArgs);
_timerManager.UpdateTimers(frameEventArgs);
_taskManager.ProcessPendingTasks();
_userInterfaceManager.Update(frameEventArgs);
// GameStateManager is in full control of the simulation update.
if (_client.RunLevel >= ClientRunLevel.Connected)
{
_componentManager.CullRemovedComponents();
_gameStateManager.ApplyGameState();
_entityManager.Update(frameEventArgs.DeltaSeconds);
_playerManager.Update(frameEventArgs.DeltaSeconds);
}
_stateManager.Update(frameEventArgs);
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
}

View File

@@ -212,12 +212,6 @@ namespace Robust.Client.GameObjects
return entity;
}
/// <inheritdoc />
public override IEntity SpawnEntityNoMapInit(string? protoName, EntityCoordinates coordinates)
{
return SpawnEntity(protoName, coordinates);
}
protected override EntityUid GenerateEntityUid()
{
return new(_nextClientEntityUid++);

View File

@@ -34,7 +34,7 @@ namespace Robust.Client.GameObjects
_networkManager.RegisterNetMessage<MsgEntity>(MsgEntity.NAME, HandleEntityNetworkMessage);
}
public void Update()
public void TickUpdate()
{
while (_queue.Count != 0 && _queue.Peek().msg.SourceTick <= _gameStateManager.CurServerTick)
{
@@ -45,12 +45,12 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message)
public void SendSystemNetworkMessage(EntityEventArgs message)
{
SendSystemNetworkMessage(message, default(uint));
}
public void SendSystemNetworkMessage(EntitySystemMessage message, uint sequence)
public void SendSystemNetworkMessage(EntityEventArgs message, uint sequence)
{
var msg = _networkManager.CreateNetMessage<MsgEntity>();
msg.Type = EntityMessageType.SystemMessage;
@@ -62,7 +62,7 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message, INetChannel channel)
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel channel)
{
throw new NotSupportedException();
}

View File

@@ -1,4 +1,4 @@
using Robust.Client.Graphics;
using Robust.Client.Graphics;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
@@ -152,6 +152,7 @@ namespace Robust.Client.GameObjects
Zoom = state.Zoom;
Offset = state.Offset;
Rotation = state.Rotation;
VisibilityMask = state.VisibilityMask;
}
public override void OnRemove()

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
@@ -19,6 +19,7 @@ using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -36,13 +37,13 @@ namespace Robust.Client.GameObjects
private bool _visible = true;
[ViewVariables(VVAccess.ReadWrite)]
public bool Visible
public override bool Visible
{
get => _visible;
set => _visible = value;
}
[DataFieldWithConstant("drawdepth", typeof(DrawDepthTag))]
[DataField("drawdepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
private int drawDepth = DrawDepthTag.Default;
/// <summary>
@@ -2084,6 +2085,7 @@ namespace Robust.Client.GameObjects
public IEntityManager EntityManager { get; } = null!;
public string Name { get; set; } = string.Empty;
public EntityUid Uid { get; } = EntityUid.Invalid;
EntityLifeStage IEntity.LifeStage { get => _lifeStage; set => _lifeStage = value; }
public bool Initialized { get; } = false;
public bool Initializing { get; } = false;
public bool Deleted { get; } = true;
@@ -2100,6 +2102,7 @@ namespace Robust.Client.GameObjects
public IMetaDataComponent MetaData { get; } = null!;
private Dictionary<Type, IComponent> _components = new();
private EntityLifeStage _lifeStage;
public T AddComponent<T>() where T : Component, new()
{
@@ -2188,11 +2191,7 @@ namespace Robust.Client.GameObjects
{
return null;
}
public void Shutdown()
{
}
public void Delete()
{
}

View File

@@ -9,7 +9,7 @@ namespace Robust.Client.GameObjects
public static class EntityManagerExt
{
public static void RaisePredictiveEvent<T>(this IEntityManager entityManager, T msg)
where T : EntitySystemMessage
where T : EntityEventArgs
{
var localPlayer = IoCManager.Resolve<IPlayerManager>().LocalPlayer;
DebugTools.AssertNotNull(localPlayer);

View File

@@ -8,7 +8,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Player;
@@ -165,6 +164,12 @@ namespace Robust.Client.GameObjects
Logger.Warning("Interrupting positional audio, can't set position.");
stream.Source.StopPlaying();
}
if (stream.TrackingEntity != null)
{
stream.Source.SetVelocity(stream.TrackingEntity.GlobalLinearVelocity());
}
}
}
}
@@ -188,7 +193,7 @@ namespace Robust.Client.GameObjects
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream? Play(string filename, AudioParams? audioParams = null)
private IPlayingAudioStream? Play(string filename, AudioParams? audioParams = null)
{
if (_resourceCache.TryGetResource<AudioResource>(new ResourcePath(filename), out var audio))
{
@@ -204,7 +209,7 @@ namespace Robust.Client.GameObjects
/// </summary>
/// <param name="stream">The audio stream to play.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream Play(AudioStream stream, AudioParams? audioParams = null)
private IPlayingAudioStream Play(AudioStream stream, AudioParams? audioParams = null)
{
var source = _clyde.CreateAudioSource(stream);
ApplyAudioParams(audioParams, source);
@@ -226,7 +231,7 @@ namespace Robust.Client.GameObjects
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream? Play(string filename, IEntity entity, AudioParams? audioParams = null)
private IPlayingAudioStream? Play(string filename, IEntity entity, AudioParams? audioParams = null)
{
if (_resourceCache.TryGetResource<AudioResource>(new ResourcePath(filename), out var audio))
{
@@ -243,7 +248,7 @@ namespace Robust.Client.GameObjects
/// <param name="stream">The audio stream to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream? Play(AudioStream stream, IEntity entity, AudioParams? audioParams = null)
private IPlayingAudioStream? Play(AudioStream stream, IEntity entity, AudioParams? audioParams = null)
{
var source = _clyde.CreateAudioSource(stream);
if (!source.SetPosition(entity.Transform.WorldPosition))
@@ -272,7 +277,7 @@ namespace Robust.Client.GameObjects
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream? Play(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
private IPlayingAudioStream? Play(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
if (_resourceCache.TryGetResource<AudioResource>(new ResourcePath(filename), out var audio))
{
@@ -289,7 +294,7 @@ namespace Robust.Client.GameObjects
/// <param name="stream">The audio stream to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
public IPlayingAudioStream? Play(AudioStream stream, EntityCoordinates coordinates,
private IPlayingAudioStream? Play(AudioStream stream, EntityCoordinates coordinates,
AudioParams? audioParams = null)
{
var source = _clyde.CreateAudioSource(stream);

View File

@@ -106,7 +106,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Event raised by a <see cref="ClientOccluderComponent"/> when it needs to be recalculated.
/// </summary>
internal sealed class OccluderDirtyEvent : EntitySystemMessage
internal sealed class OccluderDirtyEvent : EntityEventArgs
{
public OccluderDirtyEvent(IEntity sender, (GridId grid, Vector2i pos)? lastPosition, SnapGridOffset offset)
{

View File

@@ -11,6 +11,7 @@ using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.Enums;
namespace Robust.Client.GameObjects
{
@@ -42,7 +43,7 @@ namespace Robust.Client.GameObjects
{
base.Shutdown();
overlayManager.RemoveOverlay("EffectSystem");
overlayManager.RemoveOverlay(typeof(EffectOverlay));
}
public void CreateEffect(EffectSystemMessage message)
@@ -329,7 +330,6 @@ namespace Robust.Client.GameObjects
{
private readonly IPlayerManager _playerManager;
public override bool AlwaysDirty => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _unshadedShader;
@@ -337,8 +337,7 @@ namespace Robust.Client.GameObjects
private readonly IMapManager _mapManager;
private readonly IEntityManager _entityManager;
public EffectOverlay(EffectSystem owner, IPrototypeManager protoMan, IMapManager mapMan, IPlayerManager playerMan, IEntityManager entityManager) : base(
"EffectSystem")
public EffectOverlay(EffectSystem owner, IPrototypeManager protoMan, IMapManager mapMan, IPlayerManager playerMan, IEntityManager entityManager)
{
_owner = owner;
_unshadedShader = protoMan.Index<ShaderPrototype>("unshaded").Instance();

View File

@@ -157,7 +157,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Entity system message that is raised when the player changes attached entities.
/// </summary>
public class PlayerAttachSysMessage : EntitySystemMessage
public class PlayerAttachSysMessage : EntityEventArgs
{
/// <summary>
/// New entity the player is attached to.

View File

@@ -27,7 +27,7 @@ namespace Robust.Client.GameStates
private uint _nextInputCmdSeq = 1;
private readonly Queue<FullInputCmdMessage> _pendingInputs = new();
private readonly Queue<(uint sequence, GameTick sourceTick, EntitySystemMessage msg, object sessionMsg)>
private readonly Queue<(uint sequence, GameTick sourceTick, EntityEventArgs msg, object sessionMsg)>
_pendingSystemMessages
= new();
@@ -126,7 +126,7 @@ namespace Robust.Client.GameStates
_nextInputCmdSeq++;
}
public uint SystemMessageDispatched<T>(T message) where T : EntitySystemMessage
public uint SystemMessageDispatched<T>(T message) where T : EntityEventArgs
{
if (!Predicting)
{
@@ -242,65 +242,67 @@ namespace Robust.Client.GameStates
if (!Predicting) return;
using var _ = _timing.StartPastPredictionArea();
if (_pendingInputs.Count > 0)
using(var _ = _timing.StartPastPredictionArea())
{
Logger.DebugS(CVars.NetPredict.Name, "CL> Predicted:");
if (_pendingInputs.Count > 0)
{
Logger.DebugS(CVars.NetPredict.Name, "CL> Predicted:");
}
var pendingInputEnumerator = _pendingInputs.GetEnumerator();
var pendingMessagesEnumerator = _pendingSystemMessages.GetEnumerator();
var hasPendingInput = pendingInputEnumerator.MoveNext();
var hasPendingMessage = pendingMessagesEnumerator.MoveNext();
var ping = _network.ServerChannel!.Ping / 1000f + PredictLagBias; // seconds.
var targetTick = _timing.CurTick.Value + _processor.TargetBufferSize +
(int) Math.Ceiling(_timing.TickRate * ping) + PredictTickBias;
// Logger.DebugS("net.predict", $"Predicting from {_lastProcessedTick} to {targetTick}");
for (var t = _lastProcessedTick.Value + 1; t <= targetTick; t++)
{
var tick = new GameTick(t);
_timing.CurTick = tick;
while (hasPendingInput && pendingInputEnumerator.Current.Tick <= tick)
{
var inputCmd = pendingInputEnumerator.Current;
_inputManager.NetworkBindMap.TryGetKeyFunction(inputCmd.InputFunctionId, out var boundFunc);
Logger.DebugS(CVars.NetPredict.Name,
$" seq={inputCmd.InputSequence}, sub={inputCmd.SubTick}, dTick={tick}, func={boundFunc.FunctionName}, " +
$"state={inputCmd.State}");
input.PredictInputCommand(inputCmd);
hasPendingInput = pendingInputEnumerator.MoveNext();
}
while (hasPendingMessage && pendingMessagesEnumerator.Current.sourceTick <= tick)
{
var msg = pendingMessagesEnumerator.Current.msg;
_entities.EventBus.RaiseEvent(EventSource.Local, msg);
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.sessionMsg);
hasPendingMessage = pendingMessagesEnumerator.MoveNext();
}
if (t != targetTick)
{
// Don't run EntitySystemManager.TickUpdate if this is the target tick,
// because the rest of the main loop will call into it with the target tick later,
// and it won't be a past prediction.
_entitySystemManager.TickUpdate((float) _timing.TickPeriod.TotalSeconds);
((IBroadcastEventBusInternal) _entities.EventBus).ProcessEventQueue();
}
}
}
var pendingInputEnumerator = _pendingInputs.GetEnumerator();
var pendingMessagesEnumerator = _pendingSystemMessages.GetEnumerator();
var hasPendingInput = pendingInputEnumerator.MoveNext();
var hasPendingMessage = pendingMessagesEnumerator.MoveNext();
var ping = _network.ServerChannel!.Ping / 1000f + PredictLagBias; // seconds.
var targetTick = _timing.CurTick.Value + _processor.TargetBufferSize +
(int) Math.Ceiling(_timing.TickRate * ping) + PredictTickBias;
// Logger.DebugS("net.predict", $"Predicting from {_lastProcessedTick} to {targetTick}");
for (var t = _lastProcessedTick.Value + 1; t <= targetTick; t++)
{
var tick = new GameTick(t);
_timing.CurTick = tick;
while (hasPendingInput && pendingInputEnumerator.Current.Tick <= tick)
{
var inputCmd = pendingInputEnumerator.Current;
_inputManager.NetworkBindMap.TryGetKeyFunction(inputCmd.InputFunctionId, out var boundFunc);
Logger.DebugS(CVars.NetPredict.Name,
$" seq={inputCmd.InputSequence}, sub={inputCmd.SubTick}, dTick={tick}, func={boundFunc.FunctionName}, " +
$"state={inputCmd.State}");
input.PredictInputCommand(inputCmd);
hasPendingInput = pendingInputEnumerator.MoveNext();
}
while (hasPendingMessage && pendingMessagesEnumerator.Current.sourceTick <= tick)
{
var msg = pendingMessagesEnumerator.Current.msg;
_entities.EventBus.RaiseEvent(EventSource.Local, msg);
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.sessionMsg);
hasPendingMessage = pendingMessagesEnumerator.MoveNext();
}
if (t != targetTick)
{
// Don't run EntitySystemManager.Update if this is the target tick,
// because the rest of the main loop will call into it with the target tick later,
// and it won't be a past prediction.
_entitySystemManager.Update((float) _timing.TickPeriod.TotalSeconds);
((IEntityEventBus) _entities.EventBus).ProcessEventQueue();
}
}
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds);
}
private void ResetPredictedEntities(GameTick curTick)

View File

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

View File

@@ -3,6 +3,7 @@ using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -28,12 +29,12 @@ namespace Robust.Client.GameStates
/// <inheritdoc />
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
private readonly Font _font;
private readonly int _lineHeight;
private readonly List<NetEntity> _netEnts = new();
public NetEntityOverlay() : base(nameof(NetEntityOverlay))
public NetEntityOverlay()
{
IoCManager.InjectDependencies(this);
var cache = IoCManager.Resolve<IResourceCache>();
@@ -42,12 +43,12 @@ namespace Robust.Client.GameStates
_gameStateManager.GameStateApplied += HandleGameStateApplied;
}
private void HandleGameStateApplied(GameStateAppliedArgs args)
{
if(_gameTiming.InPrediction) // we only care about real server states.
return;
// Shift traffic history down one
for (var i = 0; i < _netEnts.Count; i++)
{
@@ -74,7 +75,7 @@ namespace Robust.Client.GameStates
if (netEnt.Id != entityState.Uid)
continue;
//TODO: calculate size of state and record it here.
netEnt.Traffic[^1] = 1;
netEnt.LastUpdate = gameState.ToSequence;
@@ -102,7 +103,7 @@ namespace Robust.Client.GameStates
for (int i = 0; i < _netEnts.Count; i++)
{
var netEnt = _netEnts[i];
if(_entityManager.EntityExists(netEnt.Id))
{
//TODO: Whoever is working on PVS remake, change the InPVS detection.
@@ -123,7 +124,7 @@ namespace Robust.Client.GameStates
_netEnts[i] = netEnt; // copy struct back
}
}
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
{
if (!_netManager.IsConnected)
@@ -131,7 +132,7 @@ namespace Robust.Client.GameStates
// remember, 0,0 is top left of ui with +X right and +Y down
var screenHandle = (DrawingHandleScreen)handle;
for (int i = 0; i < _netEnts.Count; i++)
{
var netEnt = _netEnts[i];
@@ -179,20 +180,19 @@ namespace Robust.Client.GameStates
return Color.Green; // Entity in PVS, but not updated recently.
}
protected override void Dispose(bool disposing)
protected override void DisposeBehavior()
{
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
base.Dispose(disposing);
base.DisposeBehavior();
}
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str, Color textColor)
{
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
foreach (var chr in str)
foreach (var rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, chr, baseLine, 1, textColor);
var advance = font.DrawChar(handle, rune, baseLine, 1, textColor);
baseLine += new Vector2(advance, 0);
}
}
@@ -238,14 +238,14 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if(bValue && !overlayMan.HasOverlay(nameof(NetEntityOverlay)))
if(bValue && !overlayMan.HasOverlay(typeof(NetEntityOverlay)))
{
overlayMan.AddOverlay(new NetEntityOverlay());
shell.WriteLine("Enabled network entity report overlay.");
}
else if(!bValue && overlayMan.HasOverlay(nameof(NetEntityOverlay)))
else if(!bValue && overlayMan.HasOverlay(typeof(NetEntityOverlay)))
{
overlayMan.RemoveOverlay(nameof(NetEntityOverlay));
overlayMan.RemoveOverlay(typeof(NetEntityOverlay));
shell.WriteLine("Disabled network entity report overlay.");
}
}

View File

@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -34,7 +36,7 @@ namespace Robust.Client.GameStates
private readonly List<(GameTick Tick, int Payload, int lag, int interp)> _history = new(HistorySize+10);
public NetGraphOverlay() : base(nameof(NetGraphOverlay))
public NetGraphOverlay()
{
IoCManager.InjectDependencies(this);
var cache = IoCManager.Resolve<IResourceCache>();
@@ -142,20 +144,20 @@ namespace Robust.Client.GameStates
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
}
protected override void Dispose(bool disposing)
protected override void DisposeBehavior()
{
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
base.Dispose(disposing);
base.DisposeBehavior();
}
private void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
{
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
foreach (var chr in str)
foreach (var rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, chr, baseLine, 1, Color.White);
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
baseLine += new Vector2(advance, 0);
}
}
@@ -183,14 +185,14 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if(bValue && !overlayMan.HasOverlay(nameof(NetGraphOverlay)))
if(bValue && !overlayMan.HasOverlay(typeof(NetGraphOverlay)))
{
overlayMan.AddOverlay(new NetGraphOverlay());
shell.WriteLine("Enabled network overlay.");
}
else if(overlayMan.HasOverlay(nameof(NetGraphOverlay)))
else if(overlayMan.HasOverlay(typeof(NetGraphOverlay)))
{
overlayMan.RemoveOverlay(nameof(NetGraphOverlay));
overlayMan.RemoveOverlay(typeof(NetGraphOverlay));
shell.WriteLine("Disabled network overlay.");
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,8 @@ using Robust.Client.ResourceManagement;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Enums;
namespace Robust.Client.Graphics.Clyde
{
@@ -68,8 +70,7 @@ namespace Robust.Client.Graphics.Clyde
RenderOverlays(OverlaySpace.ScreenSpaceBelowWorld);
_mainViewport.Eye = _eyeManager.CurrentEye;
RenderViewport(_mainViewport);
RenderViewport(_mainViewport); //Worldspace overlays are rendered here.
{
var handle = _renderHandle.DrawingHandleScreen;
var tex = _mainViewport.RenderTarget.Texture;
@@ -107,25 +108,67 @@ namespace Robust.Client.Graphics.Clyde
list.Add(overlay);
}
}
list.Sort(OverlayComparer.Instance);
foreach (var overlay in list)
{
overlay.ClydeRender(_renderHandle, space);
}
FlushRenderQueue();
list.Sort(OverlayComparer.Instance);
foreach (var overlay in list) {
if (overlay.RequestScreenTexture) {
FlushRenderQueue();
UpdateOverlayScreenTexture(space, _mainViewport.RenderTarget);
}
if (overlay.OverwriteTargetFrameBuffer()) {
ClearFramebuffer(default);
}
overlay.ClydeRender(_renderHandle, space);
FlushRenderQueue();
}
}
}
private void DrawEntitiesAndWorldOverlay(Viewport viewport, Box2 worldBounds)
private ClydeTexture? ScreenBufferTexture;
private GLHandle screenBufferHandle;
private Vector2 lastFrameSize;
/// <summary>
/// Sends SCREEN_TEXTURE to all overlays in the given OverlaySpace that request it.
/// </summary>
private bool UpdateOverlayScreenTexture(OverlaySpace space, RenderTexture texture) {
//This currently does NOT consider viewports and just grabs the current screen framebuffer. This will need to be improved upon in the future.
List<Overlay> oTargets = new List<Overlay>();
foreach (var overlay in _overlayManager.AllOverlays) {
if (overlay.RequestScreenTexture && overlay.Space == space) {
oTargets.Add(overlay);
}
}
if (oTargets.Count > 0 && ScreenBufferTexture != null) {
if (lastFrameSize != _framebufferSize) {
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
GL.TexImage2D(TextureTarget.Texture2D, 0, _hasGLSrgb ? PixelInternalFormat.Srgb8Alpha8 : PixelInternalFormat.Rgba8, _framebufferSize.X, _framebufferSize.Y, 0,
PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
}
lastFrameSize = _framebufferSize;
CopyRenderTextureToTexture(texture, ScreenBufferTexture);
foreach (Overlay overlay in oTargets) {
overlay.ScreenTexture = ScreenBufferTexture;
}
oTargets.Clear();
return true;
}
return false;
}
private void DrawEntities(Viewport viewport, Box2 worldBounds)
{
if (_eyeManager.CurrentMap == MapId.Nullspace || !_mapManager.HasMapEntity(_eyeManager.CurrentMap))
{
return;
}
RenderOverlays(OverlaySpace.WorldSpaceBelowEntities);
var screenSize = viewport.Size;
// So we could calculate the correct size of the entities based on the contents of their sprite...
@@ -267,14 +310,6 @@ namespace Robust.Client.Graphics.Clyde
_drawingSpriteList.Clear();
FlushRenderQueue();
// Cleanup remainders
foreach (var overlay in worldOverlays)
{
overlay.ClydeRender(_renderHandle, OverlaySpace.WorldSpace);
}
FlushRenderQueue();
}
[MethodImpl(MethodImplOptions.NoInlining)]
@@ -368,12 +403,21 @@ namespace Robust.Client.Graphics.Clyde
// We will also render worldspace overlays here so we can do them under / above entities as necessary
using (DebugGroup("Entities"))
{
DrawEntitiesAndWorldOverlay(viewport, worldBounds);
DrawEntities(viewport, worldBounds);
}
RenderOverlays(OverlaySpace.WorldSpaceBelowFOV);
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawFov)
{
GL.Clear(ClearBufferMask.StencilBufferBit);
GL.Enable(EnableCap.StencilTest);
GL.StencilOp(OpenToolkit.Graphics.OpenGL4.StencilOp.Keep, OpenToolkit.Graphics.OpenGL4.StencilOp.Keep, OpenToolkit.Graphics.OpenGL4.StencilOp.Replace);
GL.StencilFunc(StencilFunction.Always, 1, 0xFF);
GL.StencilMask(0xFF);
ApplyFovToBuffer(viewport, eye);
GL.StencilMask(0x00);
GL.Disable(EnableCap.StencilTest);
}
}
@@ -401,6 +445,14 @@ namespace Robust.Client.Graphics.Clyde
viewport.WallBleedIntermediateRenderTarget2.Texture,
UIBox2.FromDimensions(Vector2.Zero, ScreenSize), new Color(1, 1, 1, 0.5f));
}
RenderOverlays(OverlaySpace.WorldSpace);
GL.StencilFunc(StencilFunction.Notequal, 1, 0xFF);
GL.Disable(EnableCap.DepthTest);
RenderOverlays(OverlaySpace.WorldSpaceFOVStencil);
GL.Disable(EnableCap.StencilTest);
}
PopRenderStateFull(state);

View File

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

View File

@@ -10,6 +10,7 @@ using Robust.Shared.Map;
using Robust.Shared.Maths;
using static Robust.Client.GameObjects.ClientOccluderComponent;
using OGLTextureWrapMode = OpenToolkit.Graphics.OpenGL.TextureWrapMode;
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;
namespace Robust.Client.Graphics.Clyde
{
@@ -195,7 +196,6 @@ namespace Robust.Client.Graphics.Clyde
}
}
_lightSoftShaderHandle = LoadShaderHandle("/Shaders/Internal/light-soft.swsl");
_lightHardShaderHandle = LoadShaderHandle("/Shaders/Internal/light-hard.swsl");
_fovShaderHandle = LoadShaderHandle("/Shaders/Internal/fov.swsl");
@@ -361,16 +361,23 @@ namespace Robust.Client.Graphics.Clyde
FinalizeDepthDraw();
}
BindRenderTargetImmediate(RtToLoaded(viewport.LightRenderTarget));
CheckGlError();
GLClearColor(_lightManager.AmbientLightColor);
GL.Clear(ClearBufferMask.ColorBufferBit);
CheckGlError();
GL.Enable(EnableCap.StencilTest);
_isStencilling = true;
var (lightW, lightH) = GetLightMapSize(viewport.Size);
GL.Viewport(0, 0, lightW, lightH);
CheckGlError();
BindRenderTargetImmediate(RtToLoaded(viewport.LightRenderTarget));
CheckGlError();
GLClearColor(_lightManager.AmbientLightColor);
GL.ClearStencil(0xFF);
GL.StencilMask(0xFF);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.StencilBufferBit);
CheckGlError();
ApplyLightingFovToBuffer(viewport, eye);
var lightShader = _loadedShaders[_enableSoftShadows ? _lightSoftShaderHandle : _lightHardShaderHandle].Program;
lightShader.Use();
@@ -382,6 +389,11 @@ namespace Robust.Client.Graphics.Clyde
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.One);
CheckGlError();
GL.StencilFunc(StencilFunction.Equal, 0xFF, 0xFF);
CheckGlError();
GL.StencilOp(TKStencilOp.Keep, TKStencilOp.Keep, TKStencilOp.Keep);
CheckGlError();
var lastRange = float.NaN;
var lastPower = float.NaN;
var lastColor = new Color(float.NaN, float.NaN, float.NaN, float.NaN);
@@ -463,11 +475,11 @@ namespace Robust.Client.Graphics.Clyde
}
ResetBlendFunc();
GL.Disable(EnableCap.StencilTest);
_isStencilling = false;
CheckGlError();
ApplyLightingFovToBuffer(viewport, eye);
BlurOntoWalls(viewport, eye);
MergeWallLayer(viewport);
@@ -690,6 +702,13 @@ namespace Robust.Client.Graphics.Clyde
fovShader.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
fovShader.SetUniformMaybe("center", eye.Position.Position);
GL.StencilMask(0xFF);
CheckGlError();
GL.StencilFunc(StencilFunction.Always, 0, 0);
CheckGlError();
GL.StencilOp(TKStencilOp.Keep, TKStencilOp.Keep, TKStencilOp.Replace);
CheckGlError();
DrawBlit(viewport, fovShader);
if (_hasGLSamplerObjects)
@@ -939,7 +958,8 @@ namespace Robust.Client.Graphics.Clyde
viewport.WallMaskRenderTarget = CreateRenderTarget(viewport.Size, RenderTargetColorFormat.R8,
name: $"{viewport.Name}-{nameof(viewport.WallMaskRenderTarget)}");
viewport.LightRenderTarget = CreateRenderTarget(lightMapSize, lightMapColorFormat,
viewport.LightRenderTarget = CreateRenderTarget(lightMapSize,
new RenderTargetFormatParameters(lightMapColorFormat, hasDepthStencil: true),
lightMapSampleParameters,
$"{viewport.Name}-{nameof(viewport.LightRenderTarget)}");

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

View File

@@ -280,7 +280,7 @@ namespace Robust.Client.Graphics.Clyde
}
/// <summary>
/// Flush the render handle, processing and re-pooling all the command lists.
/// Flushes the render handle, processing and re-pooling all the command lists.
/// </summary>
private void FlushRenderQueue()
{
@@ -371,6 +371,7 @@ namespace Robust.Client.Graphics.Clyde
program.Use();
int textureUnitVal = 0;
// Assign shader parameters to uniform since they may be dirty.
foreach (var (name, value) in instance.Parameters)
{
@@ -413,6 +414,15 @@ namespace Robust.Client.Graphics.Clyde
case Matrix4 matrix4:
program.SetUniform(name, matrix4);
break;
case ClydeTexture clydeTexture:
//It's important to start at Texture6 here since DrawCommandBatch uses Texture0 and Texture1 immediately after calling this
//function! If passing in textures as uniforms ever stops working it might be since someone made it use all the way up to Texture6 too.
//Might change this in the future?
TextureUnit cTarget = TextureUnit.Texture6+textureUnitVal;
SetTexture(cTarget, ((ClydeTexture)clydeTexture).TextureId);
program.SetUniformTexture(name, cTarget);
textureUnitVal++;
break;
default:
throw new InvalidOperationException($"Unable to handle shader parameter {name}: {value}");
}

View File

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

View File

@@ -26,7 +26,7 @@ namespace Robust.Client.Graphics.Clyde
private readonly ConcurrentQueue<ClydeHandle> _textureDisposeQueue = new();
public Texture LoadTextureFromPNGStream(Stream stream, string? name = null,
public OwnedTexture LoadTextureFromPNGStream(Stream stream, string? name = null,
TextureLoadParameters? loadParams = null)
{
DebugTools.Assert(_mainThread == Thread.CurrentThread);
@@ -37,7 +37,7 @@ namespace Robust.Client.Graphics.Clyde
return LoadTextureFromImage(image, name, loadParams);
}
public Texture LoadTextureFromImage<T>(Image<T> image, string? name = null,
public OwnedTexture LoadTextureFromImage<T>(Image<T> image, string? name = null,
TextureLoadParameters? loadParams = null) where T : unmanaged, IPixel<T>
{
DebugTools.Assert(_mainThread == Thread.CurrentThread);
@@ -56,19 +56,19 @@ namespace Robust.Client.Graphics.Clyde
{
// Disable sRGB so stuff doesn't get interpreter wrong.
actualParams.Srgb = false;
var img = ApplyA8Swizzle((Image<A8>) (object) image);
using var img = ApplyA8Swizzle((Image<A8>) (object) image);
return LoadTextureFromImage(img, name, loadParams);
}
if (pixelType == typeof(L8) && !actualParams.Srgb)
{
var img = ApplyL8Swizzle((Image<L8>) (object) image);
using var img = ApplyL8Swizzle((Image<L8>) (object) image);
return LoadTextureFromImage(img, name, loadParams);
}
}
// Flip image because OpenGL reads images upside down.
var copy = FlipClone(image);
using var copy = FlipClone(image);
var texture = CreateBaseTextureInternal<T>(image.Width, image.Height, actualParams, name);
@@ -324,11 +324,13 @@ namespace Robust.Client.Graphics.Clyde
if (typeof(T) == typeof(A8))
{
SetSubImage(texture, dstTl, ApplyA8Swizzle((Image<A8>) (object) srcImage), srcBox);
return;
}
if (typeof(T) == typeof(L8))
{
SetSubImage(texture, dstTl, ApplyL8Swizzle((Image<L8>) (object) srcImage), srcBox);
return;
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -73,6 +73,14 @@ namespace Robust.Client.Graphics.Clyde
private Vector2 _lastMousePos;
public event Action<TextEventArgs>? TextEntered;
public event Action<MouseMoveEventArgs>? MouseMove;
public event Action<KeyEventArgs>? KeyUp;
public event Action<KeyEventArgs>? KeyDown;
public event Action<MouseWheelEventArgs>? MouseWheel;
public event Action<string>? CloseWindow;
public event Action? OnWindowScaleChanged;
// NOTE: in engine we pretend the framebuffer size is the screen size..
// For practical reasons like UI rendering.
public override Vector2i ScreenSize => _framebufferSize;
@@ -155,8 +163,8 @@ namespace Robust.Client.Graphics.Clyde
private bool InitWindow()
{
var width = _configurationManager.GetCVar(CVars.DisplayWidth);
var height = _configurationManager.GetCVar(CVars.DisplayHeight);
var width = ConfigurationManager.GetCVar(CVars.DisplayWidth);
var height = ConfigurationManager.GetCVar(CVars.DisplayHeight);
Monitor* monitor = null;
@@ -174,7 +182,7 @@ namespace Robust.Client.Graphics.Clyde
GLFW.WindowHint(WindowHintString.X11ClassName, "SS14");
GLFW.WindowHint(WindowHintString.X11InstanceName, "SS14");
var renderer = (Renderer) _configurationManager.GetCVar<int>(CVars.DisplayRenderer);
var renderer = (Renderer) ConfigurationManager.GetCVar<int>(CVars.DisplayRenderer);
Span<Renderer> renderers = (renderer == Renderer.Default) ? stackalloc Renderer[] {
Renderer.OpenGL33,
@@ -397,7 +405,7 @@ namespace Robust.Client.Graphics.Clyde
{
try
{
_gameController.TextEntered(new TextEventArgs(codepoint));
TextEntered?.Invoke(new TextEventArgs(codepoint));
}
catch (Exception e)
{
@@ -413,8 +421,7 @@ namespace Robust.Client.Graphics.Clyde
var delta = newPos - _lastMousePos;
_lastMousePos = newPos;
var ev = new MouseMoveEventArgs(delta, newPos);
_gameController.MouseMove(ev);
MouseMove?.Invoke(new MouseMoveEventArgs(delta, newPos));
}
catch (Exception e)
{
@@ -461,11 +468,11 @@ namespace Robust.Client.Graphics.Clyde
switch (action)
{
case InputAction.Release:
_gameController.KeyUp(ev);
KeyUp?.Invoke(ev);
break;
case InputAction.Press:
case InputAction.Repeat:
_gameController.KeyDown(ev);
KeyDown?.Invoke(ev);
break;
default:
throw new ArgumentOutOfRangeException(nameof(action), action, null);
@@ -477,7 +484,7 @@ namespace Robust.Client.Graphics.Clyde
try
{
var ev = new MouseWheelEventArgs(((float) offsetX, (float) offsetY), _lastMousePos);
_gameController.MouseWheel(ev);
MouseWheel?.Invoke(ev);
}
catch (Exception e)
{
@@ -489,7 +496,7 @@ namespace Robust.Client.Graphics.Clyde
{
try
{
_gameController.Shutdown("Window closed");
CloseWindow?.Invoke("Window closed");
}
catch (Exception e)
{
@@ -533,6 +540,7 @@ namespace Robust.Client.Graphics.Clyde
try
{
_windowScale = (xScale, yScale);
OnWindowScaleChanged?.Invoke();
}
catch (Exception e)
{

View File

@@ -87,7 +87,7 @@ namespace Robust.Client.Graphics.Clyde
{
base.Initialize();
_configurationManager.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
ConfigurationManager.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
if (!InitWindowing())
{
@@ -124,9 +124,9 @@ namespace Robust.Client.Graphics.Clyde
protected override void ReadConfig()
{
base.ReadConfig();
_lightmapDivider = _configurationManager.GetCVar(CVars.DisplayLightMapDivider);
_maxLightsPerScene = _configurationManager.GetCVar(CVars.DisplayMaxLightsPerScene);
_enableSoftShadows = _configurationManager.GetCVar(CVars.DisplaySoftShadows);
_lightmapDivider = ConfigurationManager.GetCVar(CVars.DisplayLightMapDivider);
_maxLightsPerScene = ConfigurationManager.GetCVar(CVars.DisplayMaxLightsPerScene);
_enableSoftShadows = ConfigurationManager.GetCVar(CVars.DisplaySoftShadows);
}
protected override void ReloadConfig()
@@ -238,7 +238,7 @@ namespace Robust.Client.Graphics.Clyde
private (int major, int minor)? ParseGLOverrideVersion()
{
var overrideGLVersion = _configurationManager.GetCVar(CVars.DisplayOGLOverrideVersion);
var overrideGLVersion = ConfigurationManager.GetCVar(CVars.DisplayOGLOverrideVersion);
if (string.IsNullOrEmpty(overrideGLVersion))
{
return null;
@@ -315,6 +315,11 @@ namespace Robust.Client.Graphics.Clyde
UniformConstantsUBO = new GLUniformBuffer<UniformConstants>(this, BindingIndexUniformConstants, nameof(UniformConstantsUBO));
CreateMainViewport();
screenBufferHandle = new GLHandle(GL.GenTexture());
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
ApplySampleParameters(TextureSampleParameters.Default);
ScreenBufferTexture = GenTexture(screenBufferHandle, _framebufferSize, true, null, TexturePixelType.Rgba32);
}
private void CreateMainViewport()

View File

@@ -37,6 +37,13 @@ namespace Robust.Client.Graphics.Clyde
public IClydeDebugInfo DebugInfo { get; } = new DummyDebugInfo();
public IClydeDebugStats DebugStats { get; } = new DummyDebugStats();
public event Action<TextEventArgs>? TextEntered;
public event Action<MouseMoveEventArgs>? MouseMove;
public event Action<KeyEventArgs>? KeyUp;
public event Action<KeyEventArgs>? KeyDown;
public event Action<MouseWheelEventArgs>? MouseWheel;
public event Action<string>? CloseWindow;
public Texture GetStockTexture(ClydeStockTexture stockTexture)
{
return new DummyTexture((1, 1));
@@ -86,6 +93,12 @@ namespace Robust.Client.Graphics.Clyde
remove { }
}
public event Action OnWindowScaleChanged
{
add { }
remove { }
}
public void Render()
{
// Nada.
@@ -101,7 +114,7 @@ namespace Robust.Client.Graphics.Clyde
// Nada.
}
public Texture LoadTextureFromPNGStream(Stream stream, string? name = null,
public OwnedTexture LoadTextureFromPNGStream(Stream stream, string? name = null,
TextureLoadParameters? loadParams = null)
{
using (var image = Image.Load<Rgba32>(stream))
@@ -110,7 +123,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
public Texture LoadTextureFromImage<T>(Image<T> image, string? name = null,
public OwnedTexture LoadTextureFromImage<T>(Image<T> image, string? name = null,
TextureLoadParameters? loadParams = null) where T : unmanaged, IPixel<T>
{
return new DummyTexture((image.Width, image.Height));
@@ -267,6 +280,11 @@ namespace Robust.Client.Graphics.Clyde
{
// Nada.
}
public void SetVelocity(Vector2 velocity)
{
// Nada.
}
}
private sealed class DummyBufferedAudioSource : DummyAudioSource, IClydeBufferedAudioSource

View File

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

View File

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

View File

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

View File

@@ -18,8 +18,7 @@ namespace Robust.Client.Graphics
/// </summary>
internal abstract class ClydeBase
{
[Dependency] protected readonly IConfigurationManager _configurationManager = default!;
[Dependency] protected readonly IGameControllerInternal _gameController = default!;
[Dependency] protected readonly IConfigurationManager ConfigurationManager = default!;
protected WindowMode WindowMode { get; private set; } = WindowMode.Windowed;
protected bool VSync { get; private set; } = true;
@@ -31,11 +30,11 @@ namespace Robust.Client.Graphics
public virtual bool Initialize()
{
_configurationManager.OnValueChanged(CVars.DisplayVSync, _vSyncChanged, true);
_configurationManager.OnValueChanged(CVars.DisplayWindowMode, _windowModeChanged, true);
_configurationManager.OnValueChanged(CVars.DisplayLightMapDivider, LightmapDividerChanged, true);
_configurationManager.OnValueChanged(CVars.DisplayMaxLightsPerScene, MaxLightsPerSceneChanged, true);
_configurationManager.OnValueChanged(CVars.DisplaySoftShadows, SoftShadowsChanged, true);
ConfigurationManager.OnValueChanged(CVars.DisplayVSync, _vSyncChanged, true);
ConfigurationManager.OnValueChanged(CVars.DisplayWindowMode, _windowModeChanged, true);
ConfigurationManager.OnValueChanged(CVars.DisplayLightMapDivider, LightmapDividerChanged, true);
ConfigurationManager.OnValueChanged(CVars.DisplayMaxLightsPerScene, MaxLightsPerSceneChanged, true);
ConfigurationManager.OnValueChanged(CVars.DisplaySoftShadows, SoftShadowsChanged, true);
return true;
}
@@ -51,8 +50,8 @@ namespace Robust.Client.Graphics
protected virtual void ReadConfig()
{
WindowMode = (WindowMode) _configurationManager.GetCVar(CVars.DisplayWindowMode);
VSync = _configurationManager.GetCVar(CVars.DisplayVSync);
WindowMode = (WindowMode) ConfigurationManager.GetCVar(CVars.DisplayWindowMode);
VSync = ConfigurationManager.GetCVar(CVars.DisplayVSync);
}
private void _vSyncChanged(bool newValue)

View File

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

View File

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

View File

@@ -31,10 +31,12 @@ namespace Robust.Client.Graphics
event Action<WindowFocusedEventArgs> OnWindowFocused;
Texture LoadTextureFromPNGStream(Stream stream, string? name = null,
event Action OnWindowScaleChanged;
OwnedTexture LoadTextureFromPNGStream(Stream stream, string? name = null,
TextureLoadParameters? loadParams = null);
Texture LoadTextureFromImage<T>(Image<T> image, string? name = null,
OwnedTexture LoadTextureFromImage<T>(Image<T> image, string? name = null,
TextureLoadParameters? loadParams = null) where T : unmanaged, IPixel<T>;
/// <summary>

View File

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

View File

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

View File

@@ -12,7 +12,7 @@ namespace Robust.Client.Graphics
{
IFontFaceHandle Load(Stream stream);
IFontInstanceHandle MakeInstance(IFontFaceHandle handle, int size);
void Initialize();
void SetFontDpi(uint fontDpi);
}
internal interface IFontFaceHandle

View File

@@ -1,22 +1,29 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Robust.Shared.Timing;
namespace Robust.Client.Graphics
{
[PublicAPI]
public interface IOverlayManager
{
void AddOverlay(Overlay overlay);
void RemoveOverlay(string id);
bool HasOverlay(string id);
bool AddOverlay(Overlay overlay);
Overlay GetOverlay(string id);
T GetOverlay<T>(string id) where T : Overlay;
bool RemoveOverlay(Overlay overlay);
bool RemoveOverlay(Type overlayClass);
bool RemoveOverlay<T>() where T : Overlay;
bool TryGetOverlay(string id, [NotNullWhen(true)] out Overlay? overlay);
bool TryGetOverlay<T>(string id, [NotNullWhen(true)] out T? overlay) where T : Overlay;
bool TryGetOverlay(Type overlayClass, out Overlay? overlay);
bool TryGetOverlay<T>(out T? overlay) where T : Overlay;
Overlay GetOverlay(Type overlayClass);
T GetOverlay<T>() where T : Overlay;
bool HasOverlay(Type overlayClass);
bool HasOverlay<T>() where T : Overlay;
IEnumerable<Overlay> AllOverlays { get; }
}

View File

@@ -1,65 +1,54 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.IoC;
using JetBrains.Annotations;
using Robust.Shared.Timing;
using Robust.Shared.Enums;
using System;
namespace Robust.Client.Graphics
{
/// <summary>
/// An overlay is used for fullscreen drawing in the game, for example parallax.
/// An overlay is used for fullscreen drawing in the game. This can range from drawing parallax to a full screen shader.
/// </summary>
[PublicAPI]
public abstract class Overlay
{
/// <summary>
/// The ID of this overlay. This is used to identify it inside the <see cref="IOverlayManager"/>.
/// Determines when this overlay is drawn in the rendering queue.
/// </summary>
public string ID { get; }
public virtual bool AlwaysDirty => false;
public bool IsDirty => AlwaysDirty || _isDirty;
public bool Drawing { get; private set; }
public virtual OverlaySpace Space => OverlaySpace.ScreenSpace;
/// <summary>
/// If set to true, <see cref="ScreenTexture"/> will be set to the current frame (at the moment before the overlay is rendered). This can be costly to performance, but
/// some shaders will require it as a passed in uniform to operate.
/// </summary>
public virtual bool RequestScreenTexture => false;
/// <summary>
/// If <see cref="RequestScreenTexture"> is true, then this will be set to the texture corresponding to the current frame. If false, it will always be null.
/// </summary>
public Texture? ScreenTexture = null;
/// <summary>
/// Overlays on the same OverlaySpace will be drawn from lowest ZIndex to highest ZIndex. As an example, ZIndex -1 will be drawn before ZIndex 2.
/// This value is 0 by default. Overlays with same ZIndex will be drawn in an random order.
/// </summary>
public int? ZIndex { get; set; }
protected IOverlayManager OverlayManager { get; }
public int? ZIndex { get; set; }
private bool Disposed = false;
public virtual bool SubHandlesUseMainShader { get; } = true;
private bool _isDirty = true;
private readonly List<DrawingHandleBase> TempHandles = new();
private bool Disposed;
protected Overlay(string id)
public Overlay()
{
OverlayManager = IoCManager.Resolve<IOverlayManager>();
ID = id;
}
public void Dispose()
{
if (Disposed)
{
return;
}
Dispose(true);
Disposed = true;
GC.SuppressFinalize(this);
}
~Overlay()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
/// <summary>
/// If this function returns true, the target framebuffer will be wiped before applying this overlay to it.
/// </summary>
public virtual bool OverwriteTargetFrameBuffer(){
return false;
}
/// <summary>
@@ -69,50 +58,34 @@ namespace Robust.Client.Graphics
/// <param name="currentSpace">Current space that is being drawn. This function is called for every space that was set up in initialization.</param>
protected abstract void Draw(DrawingHandleBase handle, OverlaySpace currentSpace);
public void Dirty()
{
_isDirty = true;
protected internal virtual void FrameUpdate(FrameEventArgs args) { }
~Overlay() {
Dispose();
}
public void Dispose() {
if (Disposed)
return;
else
DisposeBehavior();
}
protected virtual void DisposeBehavior(){
Disposed = true;
GC.SuppressFinalize(this);
}
protected internal virtual void FrameUpdate(FrameEventArgs args) { }
internal void ClydeRender(IRenderHandle renderHandle, OverlaySpace currentSpace)
{
DrawingHandleBase handle;
if (currentSpace == OverlaySpace.WorldSpace)
handle = renderHandle.DrawingHandleWorld;
else
if (currentSpace == OverlaySpace.ScreenSpace || currentSpace == OverlaySpace.ScreenSpaceBelowWorld)
handle = renderHandle.DrawingHandleScreen;
else
handle = renderHandle.DrawingHandleWorld;
Draw(handle, currentSpace);
}
}
/// <summary>
/// Determines in which canvas layers an overlay gets drawn.
/// </summary>
[Flags]
public enum OverlaySpace : byte
{
/// <summary>
/// Used for matching bit flags.
/// </summary>
None = 0b0000,
/// <summary>
/// This overlay will be drawn in the UI root, thus being in screen space.
/// </summary>
ScreenSpace = 0b0001,
/// <summary>
/// This overlay will be drawn in the world root, thus being in world space.
/// </summary>
WorldSpace = 0b0010,
/// <summary>
/// Drawn in screen coordinates, but behind the world.
/// </summary>
ScreenSpaceBelowWorld = 0b0100,
}
}

View File

@@ -1,13 +1,15 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Log;
using Robust.Shared.Timing;
namespace Robust.Client.Graphics
{
internal class OverlayManager : IOverlayManagerInternal
{
private readonly Dictionary<string, Overlay> _overlays = new();
private readonly Dictionary<Type, Overlay> _overlays = new Dictionary<Type, Overlay>();
public IEnumerable<Overlay> AllOverlays => _overlays.Values;
public void FrameUpdate(FrameEventArgs args)
{
@@ -17,59 +19,80 @@ namespace Robust.Client.Graphics
}
}
public void AddOverlay(Overlay overlay)
public bool AddOverlay(Overlay overlay)
{
if (_overlays.ContainsKey(overlay.ID))
{
throw new InvalidOperationException($"We already have an overlay with ID '{overlay.ID}'");
if(_overlays.ContainsKey(overlay.GetType()))
return false;
_overlays.Add(overlay.GetType(), overlay);
return true;
}
public bool RemoveOverlay(Type overlayClass)
{
if(!overlayClass.IsSubclassOf(typeof(Overlay))){
Logger.Error("RemoveOverlay was called with arg: " + overlayClass.ToString() + ", which is not a subclass of Overlay!");
return false;
}
_overlays.Add(overlay.ID, overlay);
return _overlays.Remove(overlayClass);
}
public Overlay GetOverlay(string id)
{
return _overlays[id];
public bool RemoveOverlay<T>() where T : Overlay{
return RemoveOverlay(typeof(T));
}
public T GetOverlay<T>(string id) where T : Overlay
{
return (T) GetOverlay(id);
public bool RemoveOverlay(Overlay overlay) {
return _overlays.Remove(overlay.GetType());
}
public bool HasOverlay(string id)
{
return _overlays.ContainsKey(id);
}
public void RemoveOverlay(string id)
public bool TryGetOverlay(Type overlayClass, [NotNullWhen(true)] out Overlay? overlay)
{
if (!_overlays.TryGetValue(id, out var overlay))
{
return;
overlay = null;
if (!overlayClass.IsSubclassOf(typeof(Overlay))){
Logger.Error("TryGetOverlay was called with arg: " + overlayClass.ToString() + ", which is not a subclass of Overlay!");
return false;
}
overlay.Dispose();
_overlays.Remove(id);
return _overlays.TryGetValue(overlayClass, out overlay);
}
public bool TryGetOverlay(string id, [NotNullWhen(true)] out Overlay? overlay)
{
return _overlays.TryGetValue(id, out overlay);
}
public bool TryGetOverlay<T>(string id, [NotNullWhen(true)] out T? overlay) where T : Overlay
{
if (_overlays.TryGetValue(id, out var value))
{
overlay = (T) value;
public bool TryGetOverlay<T>([NotNullWhen(true)] out T? overlay) where T : Overlay {
overlay = null;
if(_overlays.TryGetValue(typeof(T), out Overlay? toReturn)){
overlay = (T)toReturn;
return true;
}
overlay = default;
return false;
}
public IEnumerable<Overlay> AllOverlays => _overlays.Values;
public Overlay GetOverlay(Type overlayClass) {
return _overlays[overlayClass];
}
public T GetOverlay<T>() where T : Overlay {
return (T)_overlays[typeof(T)];
}
public bool HasOverlay(Type overlayClass) {
if (!overlayClass.IsSubclassOf(typeof(Overlay)))
Logger.Error("HasOverlay was called with arg: " + overlayClass.ToString() + ", which is not a subclass of Overlay!");
return _overlays.Remove(overlayClass);
}
public bool HasOverlay<T>() where T : Overlay {
return _overlays.Remove(typeof(T));
}
}
}

View File

@@ -19,10 +19,10 @@ namespace Robust.Client.Graphics
public sealed class State : IRsiStateLike
{
// List of delays for the frame to reach the next frame.
private readonly float[] Delays;
public readonly float[] Delays;
// 2D array for the texture to use for each animation frame at each direction.
private readonly Texture[][] Icons;
public readonly Texture[][] Icons;
internal State(Vector2i size, StateId stateId, DirectionType direction, float[] delays, Texture[][] icons)
{

View File

@@ -1,47 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "RSI Image Format Validation Schema V1",
"description": "Robust Station Image",
"type": "object",
"definitions": {
"size": {
"type": "object",
"properties": {
"x": {"type": "integer", "minimum": 1},
"y": {"type": "integer", "minimum": 1}
},
"required": ["x","y"]
},
"directions": {
"type": "integer",
"enum": [1,4,8]
},
"state": {
"type": "object",
"properties": {
"name": {"type": "string"},
"flags": {"type": "object"}, //To be de-serialized as a Dictionary
"directions": {"$ref": "#/definitions/directions"},
"delays": {
"type": "array",
"minItems": 1,
"items": {
"type": "array",
"items": {"type": "number", "minimum": 0, "exclusiveMinimum": true} //number == float
}
}
},
"required": ["name"] //'delays' is marked as optional in the spec
}
},
"properties": {
"version": {"type": "integer", "minimum": 1, "maximum": 1},
"size": {"$ref": "#/definitions/size"},
"states": {
"type": "array",
"items": {"$ref": "#/definitions/state"},
"minItems": 1
}
},
"required": ["version","size","states"]
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Text;
using Robust.Shared.Maths;
namespace Robust.Client.Input
@@ -58,6 +59,7 @@ namespace Robust.Client.Input
}
public uint CodePoint { get; }
public Rune AsRune => new Rune(CodePoint);
}
public class KeyEventArgs : ModifierInputEventArgs

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -67,7 +68,7 @@ namespace Robust.Client.Physics
public override void Shutdown()
{
base.Shutdown();
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(nameof(PhysicsIslandOverlay));
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsIslandOverlay));
}
private void HandleIslandSolveMessage(IslandSolveMessage message)
@@ -94,7 +95,7 @@ namespace Robust.Client.Physics
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public PhysicsIslandOverlay() : base(nameof(PhysicsIslandOverlay))
public PhysicsIslandOverlay()
{
_islandSystem = EntitySystem.Get<DebugPhysicsIslandSystem>();
_eyeManager = IoCManager.Resolve<IEyeManager>();

View File

@@ -1,5 +1,7 @@
using Robust.Shared.Enums;
using Robust.Client.Graphics;
namespace Robust.Client.Placement
{
public partial class PlacementManager
@@ -7,10 +9,9 @@ namespace Robust.Client.Placement
internal class PlacementOverlay : Overlay
{
private readonly PlacementManager _manager;
public override bool AlwaysDirty => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public PlacementOverlay(PlacementManager manager) : base("placement")
public PlacementOverlay(PlacementManager manager)
{
_manager = manager;
ZIndex = 100;

View File

@@ -21,7 +21,6 @@ namespace Robust.Client.Player
void Initialize();
void Startup(INetChannel channel);
void Update(float frameTime);
void Shutdown();
void ApplyPlayerStates(IEnumerable<PlayerState>? list);

View File

@@ -95,12 +95,6 @@ namespace Robust.Client.Player
_network.ClientSendMessage(msgList);
}
/// <inheritdoc />
public void Update(float frameTime)
{
// Uh, nothing anymore I guess.
}
/// <inheritdoc />
public void Shutdown()
{

View File

@@ -2,6 +2,7 @@
[assembly: InternalsVisibleTo("Robust.UnitTesting")]
[assembly: InternalsVisibleTo("Robust.Lite")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
#if NET5_0
[module: SkipLocalsInit]

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
@@ -53,20 +54,17 @@ namespace Robust.Client.Prototypes
private void ReloadPrototypeQueue()
{
#if !FULL_RELEASE
var then = DateTime.Now;
var sw = Stopwatch.StartNew();
var msg = NetManager.CreateNetMessage<MsgReloadPrototypes>();
msg.Paths = _reloadQueue.ToArray();
NetManager.ClientSendMessage(msg);
foreach (var path in _reloadQueue)
{
ReloadPrototypes(path);
}
ReloadPrototypes(_reloadQueue);
_reloadQueue.Clear();
Logger.Info($"Reloaded prototypes in {(int) (DateTime.Now - then).TotalMilliseconds} ms");
Logger.Info($"Reloaded prototypes in {sw.ElapsedMilliseconds} ms");
#endif
}

View File

@@ -10,5 +10,6 @@ namespace Robust.Client.ResourceManagement
void RsiLoaded(RsiLoadedEventArgs eventArgs);
void MountLoaderApi(IFileApi api, string apiPrefix, ResourcePath? prefix=null);
void PreloadTextures();
}
}

View File

@@ -0,0 +1,196 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Robust.Client.Graphics;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Log;
namespace Robust.Client.ResourceManagement
{
internal partial class ResourceCache
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
public void PreloadTextures()
{
var sawmill = _logManager.GetSawmill("res.preload");
if (!_configurationManager.GetCVar(CVars.TexturePreloadingEnabled))
{
sawmill.Debug($"Skipping texture preloading due to CVar value.");
return;
}
PreloadTextures(sawmill);
PreloadRsis(sawmill);
}
private void PreloadTextures(ISawmill sawmill)
{
sawmill.Debug("Preloading textures...");
var sw = Stopwatch.StartNew();
var resList = GetTypeDict<TextureResource>();
var texList = ContentFindFiles("/Textures/")
// Skip PNG files inside RSIs.
.Where(p => p.Extension == "png" && !p.ToString().Contains(".rsi/") && !resList.ContainsKey(p))
.Select(p => new TextureResource.LoadStepData {Path = p})
.ToArray();
Parallel.ForEach(texList, data =>
{
try
{
TextureResource.LoadPreTexture(this, data);
}
catch (Exception e)
{
// Mark failed loads as bad and skip them in the next few stages.
// Avoids any silly array resizing or similar.
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
});
foreach (var data in texList)
{
if (data.Bad)
continue;
try
{
TextureResource.LoadTexture(_clyde, data);
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
}
var errors = 0;
foreach (var data in texList)
{
if (data.Bad)
{
errors += 1;
continue;
}
try
{
var texResource = new TextureResource();
texResource.LoadFinish(this, data);
resList[data.Path] = texResource;
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
errors += 1;
}
}
sawmill.Debug(
"Preloaded {CountLoaded} textures ({CountErrored} errored) in {LoadTime}",
texList.Length,
errors,
sw.Elapsed);
}
private void PreloadRsis(ISawmill sawmill)
{
var sw = Stopwatch.StartNew();
var resList = GetTypeDict<RSIResource>();
var rsiList = ContentFindFiles("/Textures/")
.Where(p => p.ToString().EndsWith(".rsi/meta.json"))
.Select(c => c.Directory)
.Where(p => !resList.ContainsKey(p))
.Select(p => new RSIResource.LoadStepData {Path = p})
.ToArray();
Parallel.ForEach(rsiList, data =>
{
try
{
RSIResource.LoadPreTexture(this, data);
}
catch (Exception e)
{
// Mark failed loads as bad and skip them in the next few stages.
// Avoids any silly array resizing or similar.
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
});
foreach (var data in rsiList)
{
if (data.Bad)
continue;
try
{
RSIResource.LoadTexture(_clyde, data);
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
}
Parallel.ForEach(rsiList, data =>
{
if (data.Bad)
return;
try
{
RSIResource.LoadPostTexture(data);
}
catch (Exception e)
{
data.Bad = true;
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
}
});
var errors = 0;
foreach (var data in rsiList)
{
if (data.Bad)
{
errors += 1;
continue;
}
try
{
var rsiRes = new RSIResource();
rsiRes.LoadFinish(this, data);
resList[data.Path] = rsiRes;
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
errors += 1;
}
}
sawmill.Debug(
"Preloaded {CountLoaded} RSIs ({CountErrored} errored) in {LoadTime}",
rsiList.Length,
errors,
sw.Elapsed);
}
}
}

View File

@@ -2,17 +2,13 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.Utility;
using Robust.Shared.Log;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Newtonsoft.Json.Linq;
#if DEBUG
using NJsonSchema;
#endif
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -24,6 +20,14 @@ namespace Robust.Client.ResourceManagement
/// </summary>
public sealed class RSIResource : BaseResource
{
private static readonly float[] OneArray = {1};
private static readonly JsonSerializerOptions SerializerOptions =
new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
AllowTrailingCommas = true
};
public RSI RSI { get; private set; } = default!;
/// <summary>
@@ -38,64 +42,59 @@ namespace Robust.Client.ResourceManagement
public override void Load(IResourceCache cache, ResourcePath path)
{
var manifestPath = path / "meta.json";
string manifestContents;
var clyde = IoCManager.Resolve<IClyde>();
using (var manifestFile = cache.ContentFileRead(manifestPath))
using (var reader = new StreamReader(manifestFile))
{
manifestContents = reader.ReadToEnd();
}
var loadStepData = new LoadStepData {Path = path};
LoadPreTexture(cache, loadStepData);
#if DEBUG
if (RSISchema != null)
{
var errors = RSISchema.Validate(manifestContents);
if (errors.Count != 0)
{
Logger.Error($"Unable to load RSI from '{path}', {errors.Count} errors:");
// Load atlas.
LoadTexture(clyde, loadStepData);
foreach (var error in errors)
{
Logger.Error("{0}", error.ToString());
}
LoadPostTexture(loadStepData);
LoadFinish(cache, loadStepData);
throw new RSILoadException($"{errors.Count} errors while loading RSI. See console.");
}
}
#endif
loadStepData.AtlasSheet.Dispose();
}
// Ok schema validated just fine.
var manifestJson = JObject.Parse(manifestContents);
internal static void LoadTexture(IClyde clyde, LoadStepData loadStepData)
{
loadStepData.AtlasTexture = clyde.LoadTextureFromImage(
loadStepData.AtlasSheet,
loadStepData.Path.ToString());
}
var toAtlas = new List<(Image<Rgba32> src, Texture[][] output, int[][] indices, Vector2i[][] offsets, int totalFrameCount)>();
internal static void LoadPreTexture(IResourceCache cache, LoadStepData data)
{
var metadata = LoadRsiMetadata(cache, data.Path);
var metaData = ParseMetaData(manifestJson);
var frameSize = metaData.Size;
var rsi = new RSI(frameSize, path);
var stateCount = metadata.States.Length;
var toAtlas = new StateReg[stateCount];
var callbackOffsets = new Dictionary<RSI.StateId, Vector2i[][]>();
var frameSize = metadata.Size;
var rsi = new RSI(frameSize, data.Path);
var callbackOffsets = new Dictionary<RSI.StateId, Vector2i[][]>(stateCount);
// Do every state.
foreach (var stateObject in metaData.States)
for (var index = 0; index < metadata.States.Length; index++)
{
// Load image from disk.
var texPath = path / (stateObject.StateId + ".png");
var stream = cache.ContentFileRead(texPath);
Image<Rgba32> image;
using (stream)
{
image = Image.Load<Rgba32>(stream);
}
var sheetSize = new Vector2i(image.Width, image.Height);
ref var reg = ref toAtlas[index];
if (sheetSize.X % frameSize.X != 0 || sheetSize.Y % frameSize.Y != 0)
var stateObject = metadata.States[index];
// Load image from disk.
var texPath = data.Path / (stateObject.StateId + ".png");
using (var stream = cache.ContentFileRead(texPath))
{
reg.Src = Image.Load<Rgba32>(stream);
}
if (reg.Src.Width % frameSize.X != 0 || reg.Src.Height % frameSize.Y != 0)
{
throw new RSILoadException("State image size is not a multiple of the icon size.");
}
// Load all frames into a list so we can operate on it more sanely.
var frameCount = stateObject.Delays.Sum(delayList => delayList.Length);
reg.TotalFrameCount = stateObject.Delays.Sum(delayList => delayList.Length);
var (foldedDelays, foldedIndices) = FoldDelays(stateObject.Delays);
@@ -108,29 +107,34 @@ namespace Robust.Client.ResourceManagement
callbackOffset[i] = new Vector2i[foldedIndices[0].Length];
}
var state = new RSI.State(frameSize, stateObject.StateId, stateObject.DirType, foldedDelays, textures);
reg.Output = textures;
reg.Indices = foldedIndices;
reg.Offsets = callbackOffset;
var state = new RSI.State(frameSize, stateObject.StateId, stateObject.DirType, foldedDelays,
textures);
rsi.AddState(state);
toAtlas.Add((image, textures, foldedIndices, callbackOffset, frameCount));
callbackOffsets[stateObject.StateId] = callbackOffset;
}
// Poorly hacked in texture atlas support here.
var totalFrameCount = toAtlas.Sum(p => p.totalFrameCount);
var totalFrameCount = toAtlas.Sum(p => p.TotalFrameCount);
// Generate atlas.
var dimensionX = (int) MathF.Ceiling(MathF.Sqrt(totalFrameCount));
var dimensionY = (int) MathF.Ceiling((float) totalFrameCount / dimensionX);
using var sheet = new Image<Rgba32>(dimensionX * frameSize.X, dimensionY * frameSize.Y);
var sheet = new Image<Rgba32>(dimensionX * frameSize.X, dimensionY * frameSize.Y);
var sheetIndex = 0;
foreach (var (src, _, _, _, frameCount) in toAtlas)
for (var index = 0; index < toAtlas.Length; index++)
{
ref var reg = ref toAtlas[index];
// Blit all the frames over.
for (var i = 0; i < frameCount; i++)
for (var i = 0; i < reg.TotalFrameCount; i++)
{
var srcWidth = (src.Width / frameSize.X);
var srcWidth = (reg.Src.Width / frameSize.X);
var srcColumn = i % srcWidth;
var srcRow = i / srcWidth;
var srcPos = (srcColumn * frameSize.X, srcRow * frameSize.Y);
@@ -141,30 +145,49 @@ namespace Robust.Client.ResourceManagement
var srcBox = UIBox2i.FromDimensions(srcPos, frameSize);
src.Blit(srcBox, sheet, sheetPos);
reg.Src.Blit(srcBox, sheet, sheetPos);
}
sheetIndex += frameCount;
sheetIndex += reg.TotalFrameCount;
}
// Load atlas.
var texture = Texture.LoadFromImage(sheet, path.ToString());
for (var i = 0; i < toAtlas.Length; i++)
{
ref var reg = ref toAtlas[i];
reg.Src.Dispose();
}
data.Rsi = rsi;
data.AtlasSheet = sheet;
data.AtlasList = toAtlas;
data.FrameSize = frameSize;
data.DimX = dimensionX;
data.CallbackOffsets = callbackOffsets;
}
internal static void LoadPostTexture(LoadStepData data)
{
var dimX = data.DimX;
var toAtlas = data.AtlasList;
var frameSize = data.FrameSize;
var texture = data.AtlasTexture;
var sheetOffset = 0;
foreach (var (_, output, indices, offsets, frameCount) in toAtlas)
for (var toAtlasIndex = 0; toAtlasIndex < toAtlas.Length; toAtlasIndex++)
{
for (var i = 0; i < indices.Length; i++)
ref var reg = ref toAtlas[toAtlasIndex];
for (var i = 0; i < reg.Indices.Length; i++)
{
var dirIndices = indices[i];
var dirOutput = output[i];
var dirOffsets = offsets[i];
var dirIndices = reg.Indices[i];
var dirOutput = reg.Output[i];
var dirOffsets = reg.Offsets[i];
for (var j = 0; j < dirIndices.Length; j++)
{
var index = sheetOffset + dirIndices[j];
var sheetColumn = index % dimensionX;
var sheetRow = index / dimensionX;
var sheetColumn = index % dimX;
var sheetRow = index / dimX;
var sheetPos = (sheetColumn * frameSize.X, sheetRow * frameSize.Y);
dirOffsets[j] = sheetPos;
@@ -172,22 +195,104 @@ namespace Robust.Client.ResourceManagement
}
}
sheetOffset += frameCount;
sheetOffset += reg.TotalFrameCount;
}
}
foreach (var (image, _, _, _, _) in toAtlas)
{
image.Dispose();
}
RSI = rsi;
internal void LoadFinish(IResourceCache cache, LoadStepData data)
{
RSI = data.Rsi;
if (cache is IResourceCacheInternal cacheInternal)
{
cacheInternal.RsiLoaded(new RsiLoadedEventArgs(path, this, sheet, callbackOffsets));
cacheInternal.RsiLoaded(new RsiLoadedEventArgs(data.Path, this, data.AtlasSheet, data.CallbackOffsets));
}
}
private static RsiMetadata LoadRsiMetadata(IResourceCache cache, ResourcePath path)
{
var manifestPath = path / "meta.json";
string manifestContents;
using (var manifestFile = cache.ContentFileRead(manifestPath))
using (var reader = new StreamReader(manifestFile))
{
manifestContents = reader.ReadToEnd();
}
// Ok schema validated just fine.
var manifestJson = JsonSerializer.Deserialize<RsiJsonMetadata>(manifestContents, SerializerOptions);
if (manifestJson == null)
throw new RSILoadException("Manifest JSON was null!");
var size = manifestJson.Size;
var states = new StateMetadata[manifestJson.States.Length];
for (var stateI = 0; stateI < manifestJson.States.Length; stateI++)
{
var stateObject = manifestJson.States[stateI];
var stateName = stateObject.Name;
RSI.State.DirectionType directions;
int dirValue;
if (stateObject.Directions is { } dirVal)
{
dirValue = dirVal;
directions = dirVal switch
{
1 => RSI.State.DirectionType.Dir1,
4 => RSI.State.DirectionType.Dir4,
8 => RSI.State.DirectionType.Dir8,
_ => throw new RSILoadException($"Invalid direction: {dirValue} expected 1, 4 or 8")
};
}
else
{
dirValue = 1;
directions = RSI.State.DirectionType.Dir1;
}
// We can ignore selectors and flags for now,
// because they're not used yet!
// Get the lists of delays.
float[][] delays;
if (stateObject.Delays != null)
{
delays = stateObject.Delays;
if (delays.Length != dirValue)
{
throw new RSILoadException(
"DirectionsdirectionFramesList count does not match amount of delays specified.");
}
for (var i = 0; i < delays.Length; i++)
{
var delayList = delays[i];
if (delayList.Length == 0)
{
delays[i] = OneArray;
}
}
}
else
{
delays = new float[dirValue][];
// No delays specified, default to 1 frame per dir.
for (var i = 0; i < dirValue; i++)
{
delays[i] = OneArray;
}
}
states[stateI] = new StateMetadata(new RSI.StateId(stateName), directions, delays);
}
return new RsiMetadata(size, states);
}
/// <summary>
/// Folds a per-directional sets of animation delays
/// into an equivalent set of animation delays and indices that works for every direction.
@@ -336,109 +441,38 @@ namespace Robust.Client.ResourceManagement
return (floatDelays, arrayIndices);
}
internal static RsiMetadata ParseMetaData(JObject manifestJson)
internal sealed class LoadStepData
{
var size = manifestJson["size"]!.ToObject<Vector2i>();
var states = new List<StateMetadata>();
foreach (var stateObject in manifestJson["states"]!.Cast<JObject>())
{
var stateName = stateObject["name"]!.ToObject<string>()!;
RSI.State.DirectionType directions;
int dirValue;
if (stateObject.TryGetValue("directions", out var dirJToken))
{
dirValue= dirJToken.ToObject<int>();
directions = dirValue switch
{
1 => RSI.State.DirectionType.Dir1,
4 => RSI.State.DirectionType.Dir4,
8 => RSI.State.DirectionType.Dir8,
_ => throw new RSILoadException($"Invalid direction: {dirValue} expected 1, 4 or 8")
};
}
else
{
dirValue = 1;
directions = RSI.State.DirectionType.Dir1;
}
// We can ignore selectors and flags for now,
// because they're not used yet!
// Get the lists of delays.
float[][] delays;
if (stateObject.TryGetValue("delays", out var delayToken))
{
delays = delayToken.ToObject<float[][]>()!;
if (delays.Length != dirValue)
{
throw new RSILoadException(
"DirectionsdirectionFramesList count does not match amount of delays specified.");
}
for (var i = 0; i < delays.Length; i++)
{
var delayList = delays[i];
if (delayList.Length == 0)
{
delays[i] = new float[] {1};
}
}
}
else
{
delays = new float[dirValue][];
// No delays specified, default to 1 frame per dir.
for (var i = 0; i < dirValue; i++)
{
delays[i] = new float[] {1};
}
}
states.Add(new StateMetadata(new RSI.StateId(stateName), directions, delays));
}
return new RsiMetadata(size, states);
public bool Bad;
public ResourcePath Path = default!;
public Image<Rgba32> AtlasSheet = default!;
public int DimX;
public StateReg[] AtlasList = default!;
public Vector2i FrameSize;
public Dictionary<RSI.StateId, Vector2i[][]> CallbackOffsets = default!;
public Texture AtlasTexture = default!;
public RSI Rsi = default!;
}
#if DEBUG
private static readonly JsonSchema? RSISchema = GetSchema();
private static JsonSchema? GetSchema()
internal struct StateReg
{
try
{
string schema;
using (var schemaStream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream("Robust.Client.Graphics.RSI.RSISchema.json")!)
using (var schemaReader = new StreamReader(schemaStream))
{
schema = schemaReader.ReadToEnd();
}
return JsonSchema.FromJsonAsync(schema).Result;
}
catch (Exception e)
{
System.Console.WriteLine("Failed to load RSI JSON Schema!\n{0}", e);
return null;
}
public Image<Rgba32> Src;
public Texture[][] Output;
public int[][] Indices;
public Vector2i[][] Offsets;
public int TotalFrameCount;
}
#endif
internal sealed class RsiMetadata
{
public RsiMetadata(Vector2i size, List<StateMetadata> states)
public RsiMetadata(Vector2i size, StateMetadata[] states)
{
Size = size;
States = states;
}
public Vector2i Size { get; }
public List<StateMetadata> States { get; }
public StateMetadata[] States { get; }
}
internal sealed class StateMetadata
@@ -466,6 +500,17 @@ namespace Robust.Client.ResourceManagement
public float[][] Delays { get; }
}
// To be directly deserialized.
[UsedImplicitly]
private sealed record RsiJsonMetadata(Vector2i Size, StateJsonMetadata[] States)
{
}
[UsedImplicitly]
private sealed record StateJsonMetadata(string Name, int? Directions, float[][]? Delays)
{
}
}
[Serializable]

View File

@@ -1,7 +1,8 @@
using System.IO;
using System.Threading;
using Robust.Client.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -9,41 +10,52 @@ using YamlDotNet.RepresentationModel;
namespace Robust.Client.ResourceManagement
{
public class TextureResource : BaseResource
public sealed class TextureResource : BaseResource
{
public const float ClickThreshold = 0.25f;
private OwnedTexture _texture = default!;
public override ResourcePath Fallback => new("/Textures/noSprite.png");
public override ResourcePath? Fallback => new("/Textures/noSprite.png");
public Texture Texture { get; private set; } = default!;
public Texture Texture => _texture;
public override void Load(IResourceCache cache, ResourcePath path)
{
if (!cache.TryContentFileRead(path, out var stream))
{
throw new FileNotFoundException("Content file does not exist for texture");
}
var clyde = IoCManager.Resolve<IClyde>();
using (stream)
{
// Primarily for tracking down iCCP sRGB errors in the image files.
Logger.DebugS("res.tex", $"Loading texture {path}.");
var data = new LoadStepData {Path = path};
var loadParameters = _tryLoadTextureParameters(cache, path) ?? TextureLoadParameters.Default;
var manager = IoCManager.Resolve<IClyde>();
using var image = Image.Load<Rgba32>(stream);
Texture = manager.LoadTextureFromImage(image, path.ToString(), loadParameters);
if (cache is IResourceCacheInternal cacheInternal)
{
cacheInternal.TextureLoaded(new TextureLoadedEventArgs(path, image, this));
}
}
LoadPreTexture(cache, data);
LoadTexture(clyde, data);
LoadFinish(cache, data);
}
private static TextureLoadParameters? _tryLoadTextureParameters(IResourceCache cache, ResourcePath path)
internal static void LoadPreTexture(IResourceCache cache, LoadStepData data)
{
using (var stream = cache.ContentFileRead(data.Path))
{
data.Image = Image.Load<Rgba32>(stream);
}
data.LoadParameters = TryLoadTextureParameters(cache, data.Path) ?? TextureLoadParameters.Default;
}
internal static void LoadTexture(IClyde clyde, LoadStepData data)
{
data.Texture = clyde.LoadTextureFromImage(data.Image, data.Path.ToString(), data.LoadParameters);
}
internal void LoadFinish(IResourceCache cache, LoadStepData data)
{
_texture = data.Texture;
if (cache is IResourceCacheInternal cacheInternal)
{
cacheInternal.TextureLoaded(new TextureLoadedEventArgs(data.Path, data.Image, this));
}
data.Image.Dispose();
}
private static TextureLoadParameters? TryLoadTextureParameters(IResourceCache cache, ResourcePath path)
{
var metaPath = path.WithName(path.Filename + ".yml");
if (cache.TryContentFileRead(metaPath, out var stream))
@@ -70,6 +82,38 @@ namespace Robust.Client.ResourceManagement
return null;
}
public override void Reload(IResourceCache cache, ResourcePath path, CancellationToken ct = default)
{
var clyde = IoCManager.Resolve<IClyde>();
var data = new LoadStepData {Path = path};
LoadPreTexture(cache, data);
if (data.Image.Width == Texture.Width && data.Image.Height == Texture.Height)
{
// Dimensions match, rewrite texture in place.
_texture.SetSubImage(Vector2i.Zero, data.Image);
}
else
{
// Dimensions do not match, make new texture.
_texture.Dispose();
LoadTexture(clyde, data);
_texture = data.Texture;
}
data.Image.Dispose();
}
internal sealed class LoadStepData
{
public ResourcePath Path = default!;
public Image<Rgba32> Image = default!;
public TextureLoadParameters LoadParameters;
public OwnedTexture Texture = default!;
public bool Bad;
}
// TODO: Due to a bug in Roslyn, NotNullIfNotNullAttribute doesn't work.
// So this can't work with both nullables and non-nullables at the same time.
// I decided to only have it work with non-nullables as such.

View File

@@ -14,7 +14,6 @@
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
<PackageReference Include="nfluidsynth" Version="0.3.1" />
<PackageReference Include="NJsonSchema" Version="10.3.8" />
<PackageReference Include="NVorbis" Version="0.10.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.3" />
@@ -39,9 +38,6 @@
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Graphics\RSI\RSISchema.json" Condition="'$(Configuration)' == 'Debug'">
<LogicalName>Robust.Client.Graphics.RSI.RSISchema.json</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Graphics\Clyde\Shaders\*" />
</ItemGroup>

View File

@@ -9,9 +9,7 @@ namespace Robust.Client.State
State CurrentState { get; }
void RequestStateChange<T>() where T : State, new();
void Update(FrameEventArgs e);
void FrameUpdate(FrameEventArgs e);
void FormResize();
void RequestStateChange(Type type);
}
}

View File

@@ -1,4 +1,4 @@
using Robust.Shared.Timing;
using Robust.Shared.Timing;
namespace Robust.Client.State
{
@@ -14,16 +14,6 @@ namespace Robust.Client.State
/// </summary>
public abstract void Shutdown();
/// <summary>
/// Update the contents of this screen.
/// </summary>
public virtual void Update(FrameEventArgs e) { }
public virtual void FrameUpdate(FrameEventArgs e) { }
/// <summary>
/// The screen has changed size, usually from resizing window. This is called automatically right after Startup.
/// </summary>
public virtual void FormResize() { }
}
}

View File

@@ -1,4 +1,4 @@
using Robust.Shared.Log;
using Robust.Shared.Log;
using System;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
@@ -17,21 +17,11 @@ namespace Robust.Client.State
CurrentState = new DefaultState();
}
public void Update(FrameEventArgs e)
{
CurrentState?.Update(e);
}
public void FrameUpdate(FrameEventArgs e)
{
CurrentState?.FrameUpdate(e);
}
public void FormResize()
{
CurrentState?.FormResize();
}
public void RequestStateChange<T>() where T : State, new()
{
RequestStateChange(typeof(T));

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@@ -817,25 +817,7 @@ namespace Robust.Client.UserInterface
/// <summary>
/// Called when the size of the control changes.
/// </summary>
protected virtual void Resized()
{
}
internal void DoUpdate(FrameEventArgs args)
{
Update(args);
foreach (var child in Children)
{
child.DoUpdate(args);
}
}
/// <summary>
/// This is called every process frame.
/// </summary>
protected virtual void Update(FrameEventArgs args)
{
}
protected virtual void Resized() { }
internal void DoFrameUpdate(FrameEventArgs args)
{

View File

@@ -385,16 +385,16 @@ namespace Robust.Client.UserInterface.Controls
var offsetY = (int) (box.Height - font.GetHeight(UIScale)) / 2;
var baseLine = new Vector2i(0, offsetY + font.GetAscent(UIScale)) + box.TopLeft;
foreach (var chr in text)
foreach (var rune in text.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}
if (!(baseLine.X < box.Left || baseLine.X + metrics.Advance > box.Right))
{
font.DrawChar(handle, chr, baseLine, UIScale, color);
font.DrawChar(handle, rune, baseLine, UIScale, color);
}
baseLine += (metrics.Advance, 0);

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using Robust.Client.Graphics;
using Robust.Shared.Animations;
using Robust.Shared.Maths;
@@ -180,15 +181,15 @@ namespace Robust.Client.UserInterface.Controls
var baseLine = CalcBaseline();
foreach (var chr in _text)
foreach (var rune in _text.EnumerateRunes())
{
if (chr == '\n')
if (rune == new Rune('\n'))
{
newlines += 1;
baseLine = CalcBaseline();
}
var advance = font.DrawChar(handle, chr, baseLine, UIScale, actualFontColor);
var advance = font.DrawChar(handle, rune, baseLine, UIScale, actualFontColor);
baseLine += (advance, 0);
}
}
@@ -252,16 +253,16 @@ namespace Robust.Client.UserInterface.Controls
var font = ActualFont;
var height = font.GetHeight(UIScale);
foreach (var chr in _text)
foreach (var rune in _text.EnumerateRunes())
{
if (chr == '\n')
if (rune == new Rune('\n'))
{
_cachedTextWidths.Add(0);
height += font.GetLineHeight(UIScale);
}
else
{
var metrics = font.GetCharMetrics(chr, UIScale);
var metrics = font.GetCharMetrics(rune, UIScale);
if (metrics == null)
{
continue;

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Shared.Input;
@@ -23,6 +24,7 @@ namespace Robust.Client.UserInterface.Controls
public const string StyleClassLineEditNotEditable = "notEditable";
public const string StylePseudoClassPlaceholder = "placeholder";
// It is assumed that these two positions are NEVER inside a surrogate pair in the text buffer.
private int _cursorPosition;
private int _selectionStart;
private string _text = "";
@@ -126,7 +128,11 @@ namespace Robust.Client.UserInterface.Controls
get => _cursorPosition;
set
{
_cursorPosition = MathHelper.Clamp(value, 0, _text.Length);
var clamped = MathHelper.Clamp(value, 0, _text.Length);
if (_text.Length != 0 && _text.Length != clamped && !Rune.TryGetRuneAt(_text, clamped, out _))
throw new ArgumentException("Cannot set cursor inside surrogate pair.");
_cursorPosition = clamped;
_selectionStart = _cursorPosition;
}
}
@@ -134,7 +140,14 @@ namespace Robust.Client.UserInterface.Controls
public int SelectionStart
{
get => _selectionStart;
set => _selectionStart = MathHelper.Clamp(value, 0, _text.Length);
set
{
var clamped = MathHelper.Clamp(value, 0, _text.Length);
if (_text.Length != 0 && _text.Length != clamped && !Rune.TryGetRuneAt(_text, clamped, out _))
throw new ArgumentException("Cannot set cursor inside surrogate pair.");
_selectionStart = clamped;
}
}
public int SelectionLower => Math.Min(_selectionStart, _cursorPosition);
@@ -275,7 +288,7 @@ namespace Robust.Client.UserInterface.Controls
return;
}
InsertAtCursor(((char) args.CodePoint).ToString());
InsertAtCursor(args.AsRune.ToString());
}
protected internal override void KeyBindDown(GUIBoundKeyEventArgs args)
@@ -302,8 +315,16 @@ namespace Robust.Client.UserInterface.Controls
}
else if (_cursorPosition != 0)
{
_text = _text.Remove(_cursorPosition - 1, 1);
_cursorPosition -= 1;
var remPos = _cursorPosition - 1;
var remAmt = 1;
// If this is a low surrogate remove two chars to remove the whole pair.
if (char.IsLowSurrogate(_text[remPos]))
{
remPos -= 1;
remAmt = 2;
}
_text = _text.Remove(remPos, remAmt);
_cursorPosition -= remAmt;
changed = true;
}
@@ -330,7 +351,10 @@ namespace Robust.Client.UserInterface.Controls
}
else if (_cursorPosition < _text.Length)
{
_text = _text.Remove(_cursorPosition, 1);
var remAmt = 1;
if (char.IsHighSurrogate(_text[_cursorPosition]))
remAmt = 2;
_text = _text.Remove(_cursorPosition, remAmt);
changed = true;
}
@@ -352,10 +376,7 @@ namespace Robust.Client.UserInterface.Controls
}
else
{
if (_cursorPosition != 0)
{
_cursorPosition -= 1;
}
ShiftCursorLeft();
_selectionStart = _cursorPosition;
}
@@ -370,10 +391,7 @@ namespace Robust.Client.UserInterface.Controls
}
else
{
if (_cursorPosition != _text.Length)
{
_cursorPosition += 1;
}
ShiftCursorRight();
_selectionStart = _cursorPosition;
}
@@ -404,19 +422,13 @@ namespace Robust.Client.UserInterface.Controls
}
else if (args.Function == EngineKeyFunctions.TextCursorSelectLeft)
{
if (_cursorPosition != 0)
{
_cursorPosition -= 1;
}
ShiftCursorLeft();
args.Handle();
}
else if (args.Function == EngineKeyFunctions.TextCursorSelectRight)
{
if (_cursorPosition != _text.Length)
{
_cursorPosition += 1;
}
ShiftCursorRight();
args.Handle();
}
@@ -525,6 +537,32 @@ namespace Robust.Client.UserInterface.Controls
// Reset this so the cursor is always visible immediately after a keybind is pressed.
_resetCursorBlink();
void ShiftCursorLeft()
{
if (_cursorPosition == 0)
return;
_cursorPosition -= 1;
if (char.IsLowSurrogate(_text[_cursorPosition]))
_cursorPosition -= 1;
}
void ShiftCursorRight()
{
if (_cursorPosition == _text.Length)
return;
_cursorPosition += 1;
// Before you confuse yourself on "shouldn't this be high surrogate since shifting left checks low"
// (Because yes, I did myself too a week after writing it)
// char.IsLowSurrogate(_text[_cursorPosition]) means "is the cursor between a surrogate pair"
// because we ALREADY moved.
if (_cursorPosition != _text.Length && char.IsLowSurrogate(_text[_cursorPosition]))
_cursorPosition += 1;
}
}
protected internal override void KeyBindUp(GUIBoundKeyEventArgs args)
@@ -555,11 +593,11 @@ namespace Robust.Client.UserInterface.Controls
var index = 0;
var chrPosX = contentBox.Left - _drawOffset;
var lastChrPostX = contentBox.Left - _drawOffset;
foreach (var chr in _text)
foreach (var rune in _text.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
index += 1;
index += rune.Utf16SequenceLength;
continue;
}
@@ -570,7 +608,7 @@ namespace Robust.Client.UserInterface.Controls
lastChrPostX = chrPosX;
chrPosX += metrics.Advance;
index += 1;
index += rune.Utf16SequenceLength;
if (chrPosX > contentBox.Right)
{
@@ -586,6 +624,9 @@ namespace Robust.Client.UserInterface.Controls
if (index > 0 && distanceRight > distanceLeft)
{
index -= 1;
if (char.IsLowSurrogate(_text[index]))
index -= 1;
}
return index;
@@ -652,60 +693,87 @@ namespace Robust.Client.UserInterface.Controls
}
// Approach for NextWordPosition and PrevWordPosition taken from Avalonia.
private int NextWordPosition(string str, int cursor)
internal static int NextWordPosition(string str, int cursor)
{
if (cursor >= str.Length)
{
return str.Length;
}
var charClass = GetCharClass(str[cursor]);
var charClass = GetCharClass(Rune.GetRuneAt(str, cursor));
var i = cursor;
for (; i < str.Length && GetCharClass(str[i]) == charClass; i++)
{
}
for (; i < str.Length && GetCharClass(str[i]) == CharClass.Whitespace; i++)
{
}
IterForward(charClass);
IterForward(CharClass.Whitespace);
return i;
void IterForward(CharClass cClass)
{
while (i < str.Length)
{
var rune = Rune.GetRuneAt(str, i);
if (GetCharClass(rune) != cClass)
break;
i += rune.Utf16SequenceLength;
}
}
}
private int PrevWordPosition(string str, int cursor)
internal static int PrevWordPosition(string str, int cursor)
{
if (cursor == 0)
{
return 0;
}
var charClass = GetCharClass(str[cursor - 1]);
var startRune = GetRuneBackwards(str, cursor - 1);
var charClass = GetCharClass(startRune);
var i = cursor;
for (; i > 0 && GetCharClass(str[i - 1]) == charClass; i--)
{
}
IterBackward();
if (charClass == CharClass.Whitespace)
{
charClass = GetCharClass(str[i - 1]);
for (; i > 0 && GetCharClass(str[i - 1]) == charClass; i--)
{
}
if (!Rune.TryGetRuneAt(str, i - 1, out var midRune))
midRune = Rune.GetRuneAt(str, i - 2);
charClass = GetCharClass(midRune);
IterBackward();
}
return i;
void IterBackward()
{
while (i > 0)
{
var rune = GetRuneBackwards(str, i - 1);
if (GetCharClass(rune) != charClass)
break;
i -= rune.Utf16SequenceLength;
}
}
static Rune GetRuneBackwards(string str, int i)
{
return Rune.TryGetRuneAt(str, i, out var rune) ? rune : Rune.GetRuneAt(str, i - 1);
}
}
private CharClass GetCharClass(char chr)
internal static CharClass GetCharClass(Rune rune)
{
if (char.IsWhiteSpace(chr))
if (Rune.IsWhiteSpace(rune))
{
return CharClass.Whitespace;
}
if (char.IsLetterOrDigit(chr))
if (Rune.IsLetterOrDigit(rune))
{
return CharClass.AlphaNumeric;
}
@@ -713,7 +781,7 @@ namespace Robust.Client.UserInterface.Controls
return CharClass.Other;
}
private enum CharClass : byte
internal enum CharClass : byte
{
Other,
AlphaNumeric,
@@ -767,7 +835,7 @@ namespace Robust.Client.UserInterface.Controls
var posX = 0;
var actualCursorPosition = 0;
var actualSelectionStartPosition = 0;
foreach (var chr in renderedText)
foreach (var chr in renderedText.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
{
@@ -776,7 +844,7 @@ namespace Robust.Client.UserInterface.Controls
}
posX += metrics.Advance;
count += 1;
count += chr.Utf16SequenceLength;
if (count == _master._cursorPosition)
{
@@ -816,9 +884,9 @@ namespace Robust.Client.UserInterface.Controls
var baseLine = (-drawOffset, offsetY + font.GetAscent(UIScale)) +
contentBox.TopLeft;
foreach (var chr in renderedText)
foreach (var rune in renderedText.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}
@@ -832,7 +900,7 @@ namespace Robust.Client.UserInterface.Controls
// Make sure we're not off the left edge of the box.
if (baseLine.X + metrics.BearingX + metrics.Width >= contentBox.Left)
{
font.DrawChar(handle, chr, baseLine, UIScale, renderedTextColor);
font.DrawChar(handle, rune, baseLine, UIScale, renderedTextColor);
}
baseLine += (metrics.Advance, 0);

View File

@@ -163,9 +163,9 @@ namespace Robust.Client.UserInterface.Controls
var titleLength = 0;
// Get string length.
foreach (var chr in title)
foreach (var rune in title.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}
@@ -196,14 +196,14 @@ namespace Robust.Client.UserInterface.Controls
var baseLine = new Vector2(0, font.GetAscent(UIScale)) + contentBox.TopLeft;
foreach (var chr in title)
foreach (var rune in title.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}
font.DrawChar(handle, chr, baseLine, UIScale, active ? fontColorActive : fontColorInactive);
font.DrawChar(handle, rune, baseLine, UIScale, active ? fontColorActive : fontColorInactive);
baseLine += new Vector2(metrics.Advance, 0);
}
@@ -295,9 +295,9 @@ namespace Robust.Client.UserInterface.Controls
var titleLength = 0;
// Get string length.
foreach (var chr in title)
foreach (var rune in title.EnumerateRunes())
{
if (!font.TryGetCharMetrics(chr, UIScale, out var metrics))
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}

View File

@@ -219,9 +219,9 @@ namespace Robust.Client.UserInterface.Controls
{
var offset = itemSelected.GetContentOffset(Vector2.Zero);
var baseLine = offset + (hOffset, vOffset + font.GetAscent(UIScale));
foreach (var chr in item.Text)
foreach (var rune in item.Text.EnumerateRunes())
{
baseLine += (font.DrawChar(handle, chr, baseLine, UIScale, Color.White), 0);
baseLine += (font.DrawChar(handle, rune, baseLine, UIScale, Color.White), 0);
}
}

View File

@@ -2,12 +2,14 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Robust.Client.Console;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.ContentPack;
using Robust.Shared.Input;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -301,43 +303,95 @@ namespace Robust.Client.UserInterface.CustomControls
private async void _loadHistoryFromDisk()
{
CommandBar.ClearHistory();
Stream stream;
try
var sawmill = Logger.GetSawmill("dbgconsole");
var data = await Task.Run(async () =>
{
stream = _resourceManager.UserData.OpenRead(HistoryPath);
}
catch (FileNotFoundException)
{
// Nada, nothing to load in that case.
return;
}
try
{
using (var reader = new StreamReader(stream, EncodingHelpers.UTF8))
Stream? stream = null;
for (var i = 0; i < 3; i++)
{
var data = JsonConvert.DeserializeObject<List<string>>(await reader.ReadToEndAsync());
CommandBar.ClearHistory();
CommandBar.History.AddRange(data);
CommandBar.HistoryIndex = CommandBar.History.Count;
try
{
stream = _resourceManager.UserData.OpenRead(HistoryPath);
break;
}
catch (FileNotFoundException)
{
// Nada, nothing to load in that case.
return null;
}
catch (IOException)
{
// File locked probably??
await Task.Delay(10);
}
}
}
finally
{
stream?.Dispose();
}
if (stream == null)
{
sawmill.Warning("Failed to load debug console history!");
return null;
}
try
{
return await JsonSerializer.DeserializeAsync<string[]>(stream);
}
catch (Exception e)
{
sawmill.Warning("Failed to load debug console history due to exception!\n{e}");
return null;
}
finally
{
// ReSharper disable once MethodHasAsyncOverload
stream.Dispose();
}
});
if (data == null)
return;
CommandBar.ClearHistory();
CommandBar.History.AddRange(data);
CommandBar.HistoryIndex = CommandBar.History.Count;
}
private void _flushHistoryToDisk()
private async void _flushHistoryToDisk()
{
using (var stream = _resourceManager.UserData.Create(HistoryPath))
using (var writer = new StreamWriter(stream, EncodingHelpers.UTF8))
CommandBar.HistoryIndex = CommandBar.History.Count;
var sawmill = Logger.GetSawmill("dbgconsole");
var newHistory = JsonSerializer.Serialize(CommandBar.History);
await Task.Run(async () =>
{
var data = JsonConvert.SerializeObject(CommandBar.History);
CommandBar.HistoryIndex = CommandBar.History.Count;
writer.Write(data);
}
Stream? stream = null;
for (var i = 0; i < 3; i++)
{
try
{
stream = _resourceManager.UserData.Create(HistoryPath);
break;
}
catch (IOException)
{
// Probably locking.
await Task.Delay(10);
}
}
if (stream == null)
{
sawmill.Warning("Failed to save debug console history!");
return;
}
// ReSharper disable once UseAwaitUsing
using var writer = new StreamWriter(stream, EncodingHelpers.UTF8);
// ReSharper disable once MethodHasAsyncOverload
writer.Write(newHistory);
});
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
@@ -45,9 +45,9 @@ namespace Robust.Client.UserInterface.CustomControls
Visible = false;
}
protected override void Update(FrameEventArgs args)
protected override void FrameUpdate(FrameEventArgs args)
{
base.Update(args);
base.FrameUpdate(args);
if ((_gameTiming.RealTime - _lastUpdate).Seconds < 1 || !VisibleInTree)
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
@@ -51,9 +51,9 @@ namespace Robust.Client.UserInterface.CustomControls
MouseFilter = contents.MouseFilter = MouseFilterMode.Ignore;
}
protected override void Update(FrameEventArgs args)
protected override void FrameUpdate(FrameEventArgs args)
{
base.Update(args);
base.FrameUpdate(args);
if ((GameTiming.RealTime - LastUpdate).Seconds < 1 || !VisibleInTree)
{

View File

@@ -1,4 +1,4 @@
using Robust.Client.GameStates;
using Robust.Client.GameStates;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
@@ -36,9 +36,9 @@ namespace Robust.Client.UserInterface.CustomControls
HorizontalAlignment = HAlignment.Left;
}
protected override void Update(FrameEventArgs args)
protected override void FrameUpdate(FrameEventArgs args)
{
base.Update(args);
base.FrameUpdate(args);
if (!VisibleInTree)
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -67,6 +67,7 @@ namespace Robust.Client.UserInterface.CustomControls
Title = Loc.GetString("Entity Spawn Panel");
SetSize = (250, 300);
MinSize = (250, 200);

View File

@@ -1,4 +1,4 @@
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
@@ -17,7 +17,7 @@ namespace Robust.Client.UserInterface.CustomControls
ShadowOffsetYOverride = 1;
}
protected override void Update(FrameEventArgs args)
protected override void FrameUpdate(FrameEventArgs args)
{
if (!VisibleInTree)
{

View File

@@ -1,4 +1,6 @@
using Robust.Client.Graphics;
using System;
using System.Collections;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
@@ -31,9 +33,11 @@ namespace Robust.Client.UserInterface.CustomControls
// We keep track of frame times in a ring buffer.
private readonly float[] _frameTimes = new float[TrackedFrames];
private readonly BitArray _gcMarkers = new(TrackedFrames);
// Position of the last frame in the ring buffer.
private int _frameIndex;
private int _lastGCCount;
public FrameGraph(IGameTiming gameTiming)
{
@@ -49,14 +53,21 @@ namespace Robust.Client.UserInterface.CustomControls
protected override void FrameUpdate(FrameEventArgs args)
{
var gcCount = GC.CollectionCount(0);
_frameTimes[_frameIndex] = (float)_gameTiming.RealFrameTime.TotalSeconds;
_gcMarkers[_frameIndex] = gcCount > _lastGCCount;
_frameIndex = (_frameIndex + 1) % TrackedFrames;
_lastGCCount = gcCount;
}
protected internal override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
Span<Vector2> triangle = stackalloc Vector2[3];
float maxHeight = 0;
for (var i = 0; i < _frameTimes.Length; i++)
{
@@ -88,6 +99,16 @@ namespace Robust.Client.UserInterface.CustomControls
color = Color.Lime;
}
handle.DrawRect(rect, color);
var gc = _gcMarkers[currentFrameIndex];
if (gc)
{
triangle[0] = (rect.Left, 0);
triangle[1] = (rect.Right, 0);
triangle[2] = (rect.Center.X, 5);
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, triangle, Color.LightBlue);
}
}
}
}

View File

@@ -91,7 +91,7 @@ namespace Robust.Client.UserInterface.CustomControls
// Prevent window headers from getting off screen due to game window resizes.
protected override void Update(FrameEventArgs args)
protected override void FrameUpdate(FrameEventArgs args)
{
var (spaceX, spaceY) = Parent!.Size;
if (Position.Y > spaceY)

View File

@@ -17,8 +17,6 @@ namespace Robust.Client.UserInterface
void Initialize();
void InitializeTesting();
void Update(FrameEventArgs args);
void FrameUpdate(FrameEventArgs args);
/// <returns>True if a UI control was hit and the key event should not pass through past UI.</returns>

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
@@ -65,7 +66,7 @@ namespace Robust.Client.UserInterface
var wordSizePixels = 0;
// The horizontal position of the text cursor.
var posX = 0;
var lastChar = 'A';
var lastRune = new Rune('A');
// If a word is larger than maxSizeX, we split it.
// We need to keep track of some data to split it into two words.
(int breakIndex, int wordSizePixels)? forceSplitData = null;
@@ -83,14 +84,14 @@ namespace Robust.Client.UserInterface
var text = tagText.Text;
// And go over every character.
for (var i = 0; i < text.Length; i++, breakIndexCounter++)
foreach (var rune in text.EnumerateRunes())
{
var chr = text[i];
breakIndexCounter += 1;
if (IsWordBoundary(lastChar, chr) || chr == '\n')
if (IsWordBoundary(lastRune, rune) || rune == new Rune('\n'))
{
// Word boundary means we know where the word ends.
if (posX > maxSizeX && lastChar != ' ')
if (posX > maxSizeX && lastRune != new Rune(' '))
{
DebugTools.Assert(wordStartBreakIndex.HasValue,
"wordStartBreakIndex can only be null if the word begins at a new line, in which case this branch shouldn't be reached as the word would be split due to being longer than a single line.");
@@ -109,22 +110,22 @@ namespace Robust.Client.UserInterface
forceSplitData = null;
// Just manually handle newlines.
if (chr == '\n')
if (rune == new Rune('\n'))
{
LineBreaks.Add(breakIndexCounter);
Height += font.GetLineHeight(uiScale);
maxUsedWidth = Math.Max(maxUsedWidth, posX);
posX = 0;
lastChar = chr;
lastRune = rune;
wordStartBreakIndex = null;
continue;
}
}
// Uh just skip unknown characters I guess.
if (!font.TryGetCharMetrics(chr, uiScale, out var metrics))
if (!font.TryGetCharMetrics(rune, uiScale, out var metrics))
{
lastChar = chr;
lastRune = rune;
continue;
}
@@ -165,7 +166,7 @@ namespace Robust.Client.UserInterface
}
}
lastChar = chr;
lastRune = rune;
}
}
@@ -185,7 +186,7 @@ namespace Robust.Client.UserInterface
Logger.Error("wordStartBreakIndex: null (duh)");
Logger.Error($"wordSizePixels: {wordSizePixels}");
Logger.Error($"posX: {posX}");
Logger.Error($"lastChar: {lastChar}");
Logger.Error($"lastChar: {lastRune}");
Logger.Error($"forceSplitData: {forceSplitData}");
Logger.Error($"LineBreaks: {string.Join(", ", LineBreaks)}");
@@ -247,9 +248,10 @@ namespace Robust.Client.UserInterface
case FormattedMessage.TagText tagText:
{
var text = tagText.Text;
for (var i = 0; i < text.Length; i++, globalBreakCounter++)
foreach (var rune in text.EnumerateRunes())
{
var chr = text[i];
globalBreakCounter += 1;
if (lineBreakIndex < LineBreaks.Count &&
LineBreaks[lineBreakIndex] == globalBreakCounter)
{
@@ -257,7 +259,7 @@ namespace Robust.Client.UserInterface
lineBreakIndex += 1;
}
var advance = font.DrawChar(handle, chr, baseLine, uiScale, currentColorTag.Color);
var advance = font.DrawChar(handle, rune, baseLine, uiScale, currentColorTag.Color);
baseLine += new Vector2(advance, 0);
}
@@ -268,9 +270,9 @@ namespace Robust.Client.UserInterface
}
[Pure]
private static bool IsWordBoundary(char a, char b)
private static bool IsWordBoundary(Rune a, Rune b)
{
return a == ' ' || b == ' ' || a == '-' || b == '-';
return a == new Rune(' ') || b == new Rune(' ') || a == new Rune('-') || b == new Rune('-');
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Robust.Client.Console;
using Robust.Client.Graphics;
@@ -133,6 +133,7 @@ namespace Robust.Client.UserInterface
QueueMeasureUpdate(RootControl);
_displayManager.OnWindowResized += args => _updateRootSize();
_displayManager.OnWindowScaleChanged += UpdateUIScale;
StateRoot = new LayoutContainer
{
@@ -174,15 +175,9 @@ namespace Robust.Client.UserInterface
_initializeCommon();
}
public void Update(FrameEventArgs args)
{
RootControl.DoUpdate(args);
}
/// <inheritdoc />
public void FrameUpdate(FrameEventArgs args)
{
RootControl.DoFrameUpdate(args);
// Process queued style & layout updates.
while (_styleUpdateQueue.Count != 0)
{
@@ -220,6 +215,8 @@ namespace Robust.Client.UserInterface
RunArrange(control);
}
RootControl.DoFrameUpdate(args);
// count down tooltip delay if we're not showing one yet and
// are hovering the mouse over a control without moving it
if (_tooltipDelay != null && !showingTooltip)
@@ -833,7 +830,13 @@ namespace Robust.Client.UserInterface
private void _uiScaleChanged(float newValue)
{
UIScale = newValue == 0f ? DefaultUIScale : newValue;
UpdateUIScale();
}
private void UpdateUIScale()
{
var newVal = _configurationManager.GetCVar(CVars.DisplayUIScale);
UIScale = newVal == 0f ? DefaultUIScale : newVal;
if (RootControl == null)
{

View File

@@ -42,19 +42,20 @@ namespace Robust.Client.Utility
{
var dstSpan = destination.GetPixelSpan();
var dstWidth = destination.Width;
var srcHeight = sourceRect.Height;
var srcWidth = sourceRect.Width;
var (ox, oy) = destinationOffset;
for (var y = 0; y < sourceRect.Height; y++)
for (var y = 0; y < srcHeight; y++)
{
var sourceRowOffset = sourceWidth * (y + sourceRect.Top) + sourceRect.Left;
var destRowOffset = dstWidth * (y + oy) + ox;
for (var x = 0; x < sourceRect.Width; x++)
{
var pixel = source[x + sourceRowOffset];
dstSpan[x + destRowOffset] = pixel;
}
var srcRow = source[sourceRowOffset..(sourceRowOffset + srcWidth)];
var dstRow = dstSpan[destRowOffset..(destRowOffset + srcWidth)];
srcRow.CopyTo(dstRow);
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
@@ -41,6 +41,40 @@ namespace Robust.Client.Utility
return specifier.RsiStateLike().Default;
}
public static float[] FrameDelays(this SpriteSpecifier specifier) {
var resc = IoCManager.Resolve<IResourceCache>();
switch (specifier) {
case SpriteSpecifier.Rsi rsi:
if (resc.TryGetResource<RSIResource>(SpriteComponent.TextureRoot / rsi.RsiPath, out var theRsi)) {
if (theRsi.RSI.TryGetState(rsi.RsiState, out var state)) {
return state.Delays;
}
}
Logger.Error("Failed to load RSI {0}", rsi.RsiPath);
return new float[0];
default:
throw new NotImplementedException();
}
}
public static Texture[] FrameArr(this SpriteSpecifier specifier) {
var resc = IoCManager.Resolve<IResourceCache>();
switch (specifier) {
case SpriteSpecifier.Rsi rsi:
if (resc.TryGetResource<RSIResource>(SpriteComponent.TextureRoot / rsi.RsiPath, out var theRsi)) {
if (theRsi.RSI.TryGetState(rsi.RsiState, out var state)) {
return state.Icons[0];
}
}
Logger.Error("Failed to load RSI {0}", rsi.RsiPath);
return new Texture[0];
default:
throw new NotImplementedException();
}
}
public static IDirectionalTextureProvider DirFrame0(this SpriteSpecifier specifier)
{
return specifier.RsiStateLike();

View File

@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Threading;
using Prometheus;
@@ -93,8 +94,8 @@ namespace Robust.Server
private IGameLoop _mainLoop = default!;
private TimeSpan _lastTitleUpdate;
private int _lastReceivedBytes;
private int _lastSentBytes;
private long _lastReceivedBytes;
private long _lastSentBytes;
private string? _shutdownReason;
@@ -141,6 +142,10 @@ namespace Robust.Server
/// <inheritdoc />
public bool Start(Func<ILogHandler>? logHandlerFactory = null)
{
var profilePath = Path.Join(Environment.CurrentDirectory, "AAAAAAAA");
ProfileOptimization.SetProfileRoot(profilePath);
ProfileOptimization.StartProfile("AAAAAAAAAA");
_config.Initialize(true);
if (LoadConfigAndUserData)
@@ -178,6 +183,8 @@ namespace Robust.Server
_config.OverrideConVars(_commandLineArgs.CVars);
}
ProfileOptSetup.Setup(_config);
//Sets up Logging
_logHandlerFactory = logHandlerFactory;
@@ -327,6 +334,8 @@ namespace Robust.Server
WindowsTickPeriod.TimeBeginPeriod((uint) _config.GetCVar(CVars.SysWinTickPeriod));
}
GC.Collect();
return false;
}
@@ -564,7 +573,7 @@ namespace Robust.Server
}
// Pass Histogram into the IEntityManager.Update so it can do more granular measuring.
_entities.Update(frameEventArgs.DeltaSeconds, TickUsage);
_entities.TickUpdate(frameEventArgs.DeltaSeconds, TickUsage);
using (TickUsage.WithLabels("PostEngine").NewTimer())
{

View File

@@ -51,7 +51,7 @@ namespace Robust.Server.GameObjects
}
}
public class PlayerAttachSystemMessage : EntitySystemMessage
public class PlayerAttachSystemMessage : EntityEventArgs
{
public PlayerAttachSystemMessage(IEntity entity, IPlayerSession newPlayer)
{
@@ -63,7 +63,7 @@ namespace Robust.Server.GameObjects
public IPlayerSession NewPlayer { get; }
}
public class PlayerDetachedSystemMessage : EntitySystemMessage
public class PlayerDetachedSystemMessage : EntityEventArgs
{
public PlayerDetachedSystemMessage(IEntity entity)
{

View File

@@ -16,6 +16,7 @@ namespace Robust.Server.GameObjects
private Vector2 _zoom = Vector2.One/2f;
private Vector2 _offset;
private Angle _rotation;
private uint _visibilityMask;
public override bool DrawFov
{
@@ -69,9 +70,22 @@ namespace Robust.Server.GameObjects
}
}
public override uint VisibilityMask
{
get => _visibilityMask;
set
{
if(_visibilityMask == value)
return;
_visibilityMask = value;
Dirty();
}
}
public override ComponentState GetComponentState(ICommonSession player)
{
return new EyeComponentState(DrawFov, Zoom, Offset, Rotation);
return new EyeComponentState(DrawFov, Zoom, Offset, Rotation, VisibilityMask);
}
}
}

View File

@@ -8,6 +8,7 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -24,7 +25,7 @@ namespace Robust.Server.GameObjects
[DataField("visible")]
private bool _visible = true;
[DataFieldWithConstant("drawdepth", typeof(DrawDepthTag))]
[DataField("drawdepth", customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
private int _drawDepth = DrawDepthTag.Default;
[DataField("scale")]
@@ -54,17 +55,19 @@ namespace Robust.Server.GameObjects
get => _drawDepth;
set
{
if (_drawDepth == value) return;
_drawDepth = value;
Dirty();
}
}
[ViewVariables(VVAccess.ReadWrite)]
public bool Visible
public override bool Visible
{
get => _visible;
set
{
if (_visible == value) return;
_visible = value;
Dirty();
}

View File

@@ -2,7 +2,7 @@ using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
{
public sealed class EntityDeletedMessage : EntitySystemMessage
public sealed class EntityDeletedMessage : EntityEventArgs
{
public IEntity Entity { get; }

View File

@@ -12,7 +12,7 @@ namespace Robust.Server.GameObjects
/// <remarks>
/// List is empty if it's no longer intersecting any.
/// </remarks>
public sealed class TileLookupUpdateMessage : EntitySystemMessage
public sealed class TileLookupUpdateMessage : EntityEventArgs
{
public Dictionary<GridId, List<Vector2i>>? NewIndices { get; }

View File

@@ -1,20 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Server.Player;
using JetBrains.Annotations;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Players;
namespace Robust.Server.GameObjects
{
[UsedImplicitly]
public class AudioSystem : EntitySystem, IAudioSystem
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
private const int AudioDistanceRange = 25;
private uint _streamIndex;
@@ -66,147 +63,6 @@ namespace Robust.Server.GameObjects
return unchecked(_streamIndex++);
}
/// <summary>
/// Play an audio file globally, without position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="audioParams"></param>
/// <param name="predicate">The predicate that will be used to send the audio to players, or null to send to everyone.</param>
/// <param name="excludedSession">Session that won't receive the audio message.</param>
/// <param name="recipients"></param>
[Obsolete("Use the Play() overload.")]
public IPlayingAudioStream PlayGlobal(string filename, AudioParams? audioParams = null, Func<IPlayerSession, bool>? predicate = null, IPlayerSession? excludedSession = null)
{
var id = CacheIdentifier();
var msg = new PlayAudioGlobalMessage
{
FileName = filename,
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id
};
if (predicate == null && excludedSession == null)
{
RaiseNetworkEvent(msg);
return new AudioSourceServer(this, id);
}
IList<IPlayerSession> players = predicate != null ? _playerManager.GetPlayersBy(predicate) : _playerManager.GetAllPlayers();
for (var i = players.Count - 1; i >= 0; i--)
{
var player = players[i];
if (player == excludedSession)
{
players.RemoveAt(i);
continue;
}
RaiseNetworkEvent(msg, player.ConnectedClient);
}
return new AudioSourceServer(this, id, players);
}
/// <summary>
/// Play an audio file following an entity.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
/// <param name="range">The max range at which the audio will be heard. Less than or equal to 0 to send to every player.</param>
/// <param name="excludedSession">Sessions that won't receive the audio message.</param>
[Obsolete("Use the Play() overload.")]
public IPlayingAudioStream PlayFromEntity(string filename, IEntity entity, AudioParams? audioParams = null, int range = AudioDistanceRange, IPlayerSession? excludedSession = null)
{
var id = CacheIdentifier();
var msg = new PlayAudioEntityMessage
{
FileName = filename,
Coordinates = entity.Transform.Coordinates,
EntityUid = entity.Uid,
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id,
};
// send to every player
if (range <= 0 && excludedSession == null)
{
RaiseNetworkEvent(msg);
return new AudioSourceServer(this, id);
}
List<IPlayerSession> players;
if (range > 0.0f)
players = _playerManager.GetPlayersInRange(entity.Transform.Coordinates, range);
else
players = _playerManager.GetAllPlayers();
for (var i = players.Count - 1; i >= 0; i--)
{
var player = players[i];
if (player == excludedSession)
{
players.RemoveAt(i);
continue;
}
RaiseNetworkEvent(msg, player.ConnectedClient);
}
return new AudioSourceServer(this, id, players);
}
/// <summary>
/// Play an audio file at a static position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
/// <param name="range">The max range at which the audio will be heard. Less than or equal to 0 to send to every player.</param>
/// <param name="excludedSession">Session that won't receive the audio message.</param>
[Obsolete("Use the Play() overload.")]
public IPlayingAudioStream PlayAtCoords(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null, int range = AudioDistanceRange, IPlayerSession? excludedSession = null)
{
var id = CacheIdentifier();
var msg = new PlayAudioPositionalMessage
{
FileName = filename,
Coordinates = coordinates,
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id
};
if (range <= 0 && excludedSession == null)
{
RaiseNetworkEvent(msg);
return new AudioSourceServer(this, id);
}
List<IPlayerSession> players;
if (range > 0.0f)
players = _playerManager.GetPlayersInRange(coordinates, range);
else
players = _playerManager.GetAllPlayers();
for (var i = players.Count - 1; i >= 0; i--)
{
var player = players[i];
if (player == excludedSession)
{
players.RemoveAt(i);
continue;
}
RaiseNetworkEvent(msg, player.ConnectedClient);
}
return new AudioSourceServer(this, id, players);
}
/// <inheritdoc />
public int DefaultSoundRange => AudioDistanceRange;
@@ -214,7 +70,7 @@ namespace Robust.Server.GameObjects
public int OcclusionCollisionMask { get; set; }
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, AudioParams? audioParams = null)
public IPlayingAudioStream Play(Filter playerFilter, string filename, AudioParams? audioParams = null)
{
var id = CacheIdentifier();
var msg = new PlayAudioGlobalMessage
@@ -234,7 +90,7 @@ namespace Robust.Server.GameObjects
}
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, IEntity entity, AudioParams? audioParams = null)
public IPlayingAudioStream Play(Filter playerFilter, string filename, IEntity entity, AudioParams? audioParams = null)
{
//TODO: Calculate this from PAS
var range = audioParams is null || audioParams.Value.MaxDistance <= 0 ? AudioDistanceRange : audioParams.Value.MaxDistance;
@@ -254,7 +110,7 @@ namespace Robust.Server.GameObjects
var recipients = (playerFilter as IFilter).Recipients;
if (range > 0.0f)
players = PASInRange(recipients, entity.Transform.MapPosition, range);
players = PasInRange(recipients, entity.Transform.MapPosition, range);
else
players = recipients;
@@ -267,7 +123,7 @@ namespace Robust.Server.GameObjects
}
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
public IPlayingAudioStream Play(Filter playerFilter, string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
//TODO: Calculate this from PAS
var range = audioParams is null || audioParams.Value.MaxDistance <= 0 ? AudioDistanceRange : audioParams.Value.MaxDistance;
@@ -285,7 +141,7 @@ namespace Robust.Server.GameObjects
var recipients = (playerFilter as IFilter).Recipients;
if (range > 0.0f)
players = PASInRange(recipients, coordinates.ToMap(EntityManager), range);
players = PasInRange(recipients, coordinates.ToMap(EntityManager), range);
else
players = recipients;
@@ -297,7 +153,7 @@ namespace Robust.Server.GameObjects
return new AudioSourceServer(this, id, players);
}
private static List<ICommonSession> PASInRange(IEnumerable<ICommonSession> players, MapCoordinates position, float range)
private static List<ICommonSession> PasInRange(IEnumerable<ICommonSession> players, MapCoordinates position, float range)
{
return players.Where(x =>
x.AttachedEntity != null &&

View File

@@ -1,4 +1,6 @@
using JetBrains.Annotations;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
@@ -10,11 +12,19 @@ namespace Robust.Server.GameObjects
public class PhysicsSystem : SharedPhysicsSystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
public override void Initialize()
{
base.Initialize();
_mapManager.OnGridCreated += HandleGridCreated;
LoadMetricCVar();
_configurationManager.OnValueChanged(CVars.MetricsEnabled, _ => LoadMetricCVar());
}
private void LoadMetricCVar()
{
MetricsEnabled = _configurationManager.GetCVar(CVars.MetricsEnabled);
}
public override void Shutdown()

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -132,14 +132,6 @@ namespace Robust.Server.GameObjects
return entity;
}
/// <inheritdoc />
public override IEntity SpawnEntityNoMapInit(string? protoName, EntityCoordinates coordinates)
{
var newEnt = CreateEntityUninitialized(protoName, coordinates);
InitializeAndStartEntity((Entity) newEnt);
return newEnt;
}
/// <inheritdoc />
public List<EntityState>? GetEntityStates(GameTick fromTick, IPlayerSession player)
{
@@ -479,8 +471,11 @@ namespace Robust.Server.GameObjects
// scan pvs box and include children and parents recursively
IncludeRelatives(GetEntitiesInRange(mapId, position, range, true), relatives);
if (player.AttachedEntity is null || !player.AttachedEntity.TryGetComponent<EyeComponent>(out var eyeComp))
eyeComp = null;
// Exclude any entities that are currently invisible to the player.
ExcludeInvisible(relatives, player.VisibilityMask);
ExcludeInvisible(relatives, (int)(eyeComp?.VisibilityMask ?? 0));
// Always send updates for all grid and map entities.
// If we don't, the client-side game state manager WILL blow up.
@@ -955,9 +950,9 @@ namespace Robust.Server.GameObjects
});
}
public override void Update(float frameTime, Histogram? histogram)
public override void TickUpdate(float frameTime, Histogram? histogram)
{
base.Update(frameTime, histogram);
base.TickUpdate(frameTime, histogram);
EntitiesCount.Set(AllEntities.Count);
}

View File

@@ -47,7 +47,7 @@ namespace Robust.Server.GameObjects
_configurationManager.OnValueChanged(CVars.NetLogLateMsg, b => _logLateMsgs = b, true);
}
public void Update()
public void TickUpdate()
{
while (_queue.Count != 0 && _queue.Peek().SourceTick <= _gameTiming.CurTick)
{
@@ -87,7 +87,7 @@ namespace Robust.Server.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message)
public void SendSystemNetworkMessage(EntityEventArgs message)
{
var newMsg = _networkManager.CreateNetMessage<MsgEntity>();
newMsg.Type = EntityMessageType.SystemMessage;
@@ -98,7 +98,7 @@ namespace Robust.Server.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message, INetChannel targetConnection)
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel targetConnection)
{
var newMsg = _networkManager.CreateNetMessage<MsgEntity>();
newMsg.Type = EntityMessageType.SystemMessage;

View File

@@ -9,12 +9,6 @@ namespace Robust.Server.Player
{
DateTime ConnectedTime { get; }
/// <summary>
/// The visibility mask for this player.
/// The player will be able to get updates for entities whose layers match the mask.
/// </summary>
int VisibilityMask { get; set; }
event EventHandler<SessionStatusEventArgs> PlayerStatusChanged;
void JoinGame();
@@ -27,7 +21,7 @@ namespace Robust.Server.Player
/// Do not call this directly for most content code.
/// </summary>
/// <param name="a">The entity to attach to.</param>
void AttachToEntity(IEntity a);
void AttachToEntity(IEntity? a);
/// <summary>
/// Detaches this player from an entity.

View File

@@ -1,8 +1,8 @@
using Robust.Shared.GameStates;
using Robust.Server.GameObjects;
using System;
using Robust.Server.GameObjects;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
@@ -43,7 +43,7 @@ namespace Robust.Server.Player
private SessionStatus _status = SessionStatus.Connecting;
/// <inheritdoc />
[ViewVariables]
internal string Name { get; set; }
@@ -109,7 +109,7 @@ namespace Robust.Server.Player
public event EventHandler<SessionStatusEventArgs>? PlayerStatusChanged;
/// <inheritdoc />
public void AttachToEntity(IEntity a)
public void AttachToEntity(IEntity? a)
{
DetachFromEntity();

View File

@@ -1,4 +1,4 @@
using System;
using System.Diagnostics;
using Robust.Server.Console;
using Robust.Server.Player;
using Robust.Shared.IoC;
@@ -14,7 +14,7 @@ namespace Robust.Server.Prototypes
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IConGroupController _conGroups = default!;
public ServerPrototypeManager() : base()
public ServerPrototypeManager()
{
RegisterIgnore("shader");
}
@@ -35,14 +35,11 @@ namespace Robust.Server.Prototypes
return;
}
var then = DateTime.Now;
var sw = Stopwatch.StartNew();
foreach (var path in msg.Paths)
{
ReloadPrototypes(path);
}
ReloadPrototypes(msg.Paths);
Logger.Info($"Reloaded prototypes in {(int) (DateTime.Now - then).TotalMilliseconds} ms");
Logger.Info($"Reloaded prototypes in {sw.ElapsedMilliseconds} ms");
#endif
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace Robust.Shared.Maths
@@ -16,12 +17,12 @@ namespace Robust.Shared.Maths
/// <summary>
/// The X component of the Vector2i.
/// </summary>
public int X;
[JsonInclude] public int X;
/// <summary>
/// The Y component of the Vector2i.
/// </summary>
public int Y;
[JsonInclude] public int Y;
/// <summary>
/// Construct a vector from its coordinates.

View File

@@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace Robust.Shared.Maths
@@ -12,12 +13,12 @@ namespace Robust.Shared.Maths
/// <summary>
/// The X component of the Vector2i.
/// </summary>
public uint X;
[JsonInclude] public uint X;
/// <summary>
/// The Y component of the Vector2i.
/// </summary>
public uint Y;
[JsonInclude] public uint Y;
/// <summary>
/// Construct a vector from its coordinates.

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