Compare commits

..

2 Commits

Author SHA1 Message Date
Silver
d49075a970 Update and clean up Prototype manager methods 2022-02-11 00:27:34 -07:00
Silver
a67acd453c Split out Interface and Attribute 2022-02-11 00:26:58 -07:00
207 changed files with 4543 additions and 8007 deletions

2
.github/CODEOWNERS vendored
View File

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

View File

@@ -22,7 +22,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
dotnet-version: 6.0.100
- name: Install dependencies
run: dotnet restore
- name: Build

2
.gitignore vendored
View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 535 B

View File

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

View File

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

View File

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

View File

@@ -26,6 +26,7 @@ namespace Robust.Client
[Dependency] private readonly IPlayerManager _playMan = default!;
[Dependency] private readonly INetConfigurationManager _configManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IEntityLookup _entityLookup = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IDiscordRichPresence _discord = default!;
[Dependency] private readonly IGameTiming _timing = default!;
@@ -212,6 +213,7 @@ namespace Robust.Client
{
_entityManager.Startup();
_mapManager.Startup();
_entityLookup.Startup();
_timing.ResetSimTime();
_timing.Paused = false;
@@ -222,6 +224,7 @@ namespace Robust.Client
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
_gameStates.Reset();
_playMan.Shutdown();
_entityLookup.Shutdown();
_entityManager.Shutdown();
_mapManager.Shutdown();
_discord.ClearPresence();

View File

@@ -47,6 +47,7 @@ namespace Robust.Client
IoCManager.Register<IMapManagerInternal, NetworkedMapManager>();
IoCManager.Register<INetworkedMapManager, NetworkedMapManager>();
IoCManager.Register<IEntityManager, ClientEntityManager>();
IoCManager.Register<IEntityLookup, EntityLookup>();
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
@@ -71,6 +72,7 @@ namespace Robust.Client
IoCManager.Register<IStateManager, StateManager>();
IoCManager.Register<IUserInterfaceManager, UserInterfaceManager>();
IoCManager.Register<IUserInterfaceManagerInternal, UserInterfaceManager>();
IoCManager.Register<IDebugDrawing, DebugDrawing>();
IoCManager.Register<ILightManager, LightManager>();
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IMidiManager, MidiManager>();

View File

@@ -109,13 +109,13 @@ namespace Robust.Client.Console
if (AvailableCommands.ContainsKey(commandName))
{
var playerManager = IoCManager.Resolve<IPlayerManager>();
#if !DEBUG
if (!_conGroup.CanCommand(commandName) && playerManager.LocalPlayer?.Session.Status > SessionStatus.Connecting)
{
WriteError(null, $"Insufficient perms for command: {commandName}");
return;
}
#endif
var command1 = AvailableCommands[commandName];
args.RemoveAt(0);
var shell = new ConsoleShell(this, null);

View File

@@ -168,7 +168,7 @@ namespace Robust.Client.Console.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = EntitySystem.Get<DebugDrawingSystem>();
var mgr = IoCManager.Resolve<IDebugDrawing>();
mgr.DebugPositions = !mgr.DebugPositions;
}
}
@@ -861,7 +861,7 @@ namespace Robust.Client.Console.Commands
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
var chunk = internalGrid.GetChunk(chunkIndex);
shell.WriteLine($"worldBounds: {internalGrid.CalcWorldAABB(chunk)} localBounds: {chunk.CachedBounds}");
shell.WriteLine($"worldBounds: {chunk.CalcWorldAABB()} localBounds: {chunk.CalcLocalBounds()}");
}
}

View File

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

View File

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

View File

@@ -50,6 +50,7 @@ namespace Robust.Client
[Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly ITimerManager _timerManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!;
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IOverlayManagerInternal _overlayManager = default!;
@@ -454,7 +455,6 @@ namespace Robust.Client
private void Tick(FrameEventArgs frameEventArgs)
{
_modLoader.BroadcastUpdate(ModUpdateLevel.PreEngine, frameEventArgs);
_console.CommandBufferExecute();
_timerManager.UpdateTimers(frameEventArgs);
_taskManager.ProcessPendingTasks();
@@ -470,6 +470,7 @@ namespace Robust.Client
// The last real tick is the current tick! This way we won't be in "prediction" mode.
_gameTiming.LastRealTick = _gameTiming.CurTick;
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, noPredictions: false);
_lookup.Update();
}
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
@@ -509,7 +510,7 @@ namespace Robust.Client
logManager.GetSawmill("discord").Level = LogLevel.Warning;
logManager.GetSawmill("net.predict").Level = LogLevel.Info;
logManager.GetSawmill("szr").Level = LogLevel.Info;
logManager.GetSawmill("loc").Level = LogLevel.Warning;
logManager.GetSawmill("loc").Level = LogLevel.Error;
#if DEBUG_ONLY_FCE_INFO
#if DEBUG_ONLY_FCE_LOG
@@ -574,6 +575,7 @@ namespace Robust.Client
_networkManager.Shutdown("Client shutting down");
_midiManager.Shutdown();
IoCManager.Resolve<IEntityLookup>().Shutdown();
_entityManager.Shutdown();
_clyde.Shutdown();
_clydeAudio.Shutdown();

View File

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

View File

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

View File

@@ -25,6 +25,8 @@ namespace Robust.Client.GameObjects
RSI.State.Direction EffectiveDirection(Angle worldRotation);
Vector2 LocalToLayer(Vector2 localPos);
/// <summary>
/// Layer size in pixels.
/// Don't account layer scale or sprite world transform.
@@ -35,6 +37,6 @@ namespace Robust.Client.GameObjects
/// Calculate layer bounding box in sprite local-space coordinates.
/// </summary>
/// <returns>Bounding box in sprite local-space coordinates.</returns>
Box2 CalculateBoundingBox(Angle worldAngle);
Box2 CalculateBoundingBox();
}
}

View File

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

View File

@@ -20,7 +20,6 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
using RSIDirection = Robust.Client.Graphics.RSI.State.Direction;
namespace Robust.Client.GameObjects
{
@@ -32,8 +31,6 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IResourceCache resourceCache = default!;
[Dependency] private readonly IPrototypeManager prototypes = default!;
[Dependency] private readonly IEntityManager entities = default!;
[Dependency] private readonly IReflectionManager reflection = default!;
[Dependency] private readonly IEyeManager eyeManager = default!;
[DataField("visible")]
private bool _visible = true;
@@ -75,11 +72,7 @@ namespace Robust.Client.GameObjects
public Vector2 Scale
{
get => scale;
set
{
scale = value;
UpdateLocalMatrix();
}
set => scale = value;
}
[DataField("rotation")]
@@ -90,11 +83,7 @@ namespace Robust.Client.GameObjects
public Angle Rotation
{
get => rotation;
set
{
rotation = value;
UpdateLocalMatrix();
}
set => rotation = value;
}
[DataField("offset")]
@@ -108,18 +97,12 @@ namespace Robust.Client.GameObjects
public Vector2 Offset
{
get => offset;
set
{
offset = value;
UpdateLocalMatrix();
}
set => offset = value;
}
[DataField("color")]
private Color color = Color.White;
public Matrix3 LocalMatrix = Matrix3.Identity;
[Animatable]
[ViewVariables(VVAccess.ReadWrite)]
public Color Color
@@ -151,7 +134,113 @@ namespace Robust.Client.GameObjects
Layers.Clear();
foreach (var layerDatum in value)
{
AddLayer(layerDatum);
var anyTextureAttempted = false;
var layer = new Layer(this);
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
{
var path = TextureRoot / layerDatum.RsiPath;
if (IoCManager.Resolve<IResourceCache>().TryGetResource(path, out RSIResource? resource))
{
layer.RSI = resource.RSI;
}
else
{
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.State))
{
anyTextureAttempted = true;
var theRsi = layer.RSI ?? BaseRSI;
if (theRsi == null)
{
Logger.ErrorS(LogCategory,
"Layer has no RSI to load states from. Cannot use 'state' property. ({0})",
layerDatum.State);
}
else
{
var stateid = new RSI.StateId(layerDatum.State);
layer.State = stateid;
if (theRsi.TryGetState(stateid, out var state))
{
// Always use south because this layer will be cached in the serializer.
layer.AnimationTimeLeft = state.GetDelay(0);
}
else
{
Logger.ErrorS(LogCategory,
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
stateid);
}
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
{
anyTextureAttempted = true;
if (layer.State.IsValid)
{
Logger.ErrorS(LogCategory,
"Cannot specify 'texture' on a layer if it has an RSI state specified."
);
}
else
{
layer.Texture =
IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.Shader))
{
if (IoCManager.Resolve<IPrototypeManager>().TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
{
layer.Shader = prototype.Instance();
}
else
{
Logger.ErrorS(LogCategory,
"Shader prototype '{0}' does not exist.",
layerDatum.Shader);
}
}
layer.Color = layerDatum.Color;
layer.Rotation = layerDatum.Rotation;
layer._offset = layerDatum.Offset;
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
layer.Visible = anyTextureAttempted && layerDatum.Visible;
layer.Scale = layerDatum.Scale;
Layers.Add(layer);
if (layerDatum.MapKeys != null)
{
var index = Layers.Count - 1;
foreach (var keyString in layerDatum.MapKeys)
{
object key;
if (IoCManager.Resolve<IReflectionManager>().TryParseEnumReference(keyString, out var @enum))
{
key = @enum;
}
else
{
key = keyString;
}
if (LayerMap.ContainsKey(key))
{
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
continue;
}
LayerMap.Add(key, index);
}
}
}
_layerMapShared = true;
@@ -247,13 +336,11 @@ namespace Robust.Client.GameObjects
void ISerializationHooks.AfterDeserialization()
{
IoCManager.InjectDependencies(this);
{
if (!string.IsNullOrWhiteSpace(rsi))
{
var rsiPath = TextureRoot / rsi;
if(resourceCache.TryGetResource(rsiPath, out RSIResource? resource))
if(IoCManager.Resolve<IResourceCache>().TryGetResource(rsiPath, out RSIResource? resource))
{
BaseRSI = resource.RSI;
}
@@ -286,8 +373,6 @@ namespace Robust.Client.GameObjects
LayerMap.Clear();
LayerDatums = layerDatums;
}
UpdateLocalMatrix();
}
/// <summary>
@@ -305,7 +390,6 @@ namespace Robust.Client.GameObjects
offset = other.offset;
rotation = other.rotation;
scale = other.scale;
UpdateLocalMatrix();
drawDepth = other.drawDepth;
_screenLock = other._screenLock;
_overrideDirection = other._overrideDirection;
@@ -331,14 +415,9 @@ namespace Robust.Client.GameObjects
RenderOrder = other.RenderOrder;
}
internal void UpdateLocalMatrix()
{
LocalMatrix = Matrix3.CreateTransform(in offset, in rotation, in scale);
}
public Matrix3 GetLocalMatrix()
{
return LocalMatrix;
return Matrix3.CreateTransform(in offset, in rotation, in scale);
}
/// <inheritdoc />
@@ -383,17 +462,14 @@ namespace Robust.Client.GameObjects
_layerMapShared = false;
}
public int LayerMapReserveBlank(object key)
public void LayerMapReserveBlank(object key)
{
if (LayerMapTryGet(key, out var index))
if (LayerMapTryGet(key, out var _))
{
return index;
return;
}
index = AddBlankLayer();
LayerMapSet(key, index);
return index;
LayerMapSet(key, AddBlankLayer());
}
public int AddBlankLayer(int? newIndex = null)
@@ -402,19 +478,6 @@ namespace Robust.Client.GameObjects
return AddLayer(layer, newIndex);
}
/// <summary>
/// Add a new layer based on some <see cref="PrototypeLayerData"/>.
/// </summary>
public int AddLayer(PrototypeLayerData layerDatum, int? newIndex = null)
{
var layer = new Layer(this);
var index = AddLayer(layer, newIndex);
LayerSetData(index, layerDatum);
return index;
}
public int AddLayer(string texturePath, int? newIndex = null)
{
return AddLayer(new ResourcePath(texturePath), newIndex);
@@ -590,144 +653,7 @@ namespace Robust.Client.GameObjects
RemoveLayer(layer);
}
/// <summary>
/// Fills in a layer's values using some <see cref="PrototypeLayerData"/>.
/// </summary>
public void LayerSetData(int index, PrototypeLayerData layerDatum)
{
if (Layers.Count <= index)
{
Logger.ErrorS(LogCategory, "Layer with index '{0}' does not exist, cannot set layer data! Trace:\n{1}",
index, Environment.StackTrace);
return;
}
var layer = Layers[index];
var anyTextureAttempted = false;
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
{
var path = TextureRoot / layerDatum.RsiPath;
if (resourceCache.TryGetResource(path, out RSIResource? resource))
{
layer.RSI = resource.RSI;
}
else
{
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.State))
{
anyTextureAttempted = true;
var theRsi = layer.RSI ?? BaseRSI;
if (theRsi == null)
{
Logger.ErrorS(LogCategory,
"Layer has no RSI to load states from. Cannot use 'state' property. ({0})",
layerDatum.State);
}
else
{
var stateid = new RSI.StateId(layerDatum.State);
layer.State = stateid;
if (theRsi.TryGetState(stateid, out var state))
{
// Always use south because this layer will be cached in the serializer.
layer.AnimationTimeLeft = state.GetDelay(0);
}
else
{
Logger.ErrorS(LogCategory,
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
stateid);
}
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
{
anyTextureAttempted = true;
if (layer.State.IsValid)
{
Logger.ErrorS(LogCategory,
"Cannot specify 'texture' on a layer if it has an RSI state specified."
);
}
else
{
layer.Texture =
resourceCache.GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.Shader))
{
if (prototypes.TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
{
layer.ShaderPrototype = layerDatum.Shader;
layer.Shader = prototype.Instance();
}
else
{
Logger.ErrorS(LogCategory,
"Shader prototype '{0}' does not exist.",
layerDatum.Shader);
}
}
if (layerDatum.MapKeys != null)
{
foreach (var keyString in layerDatum.MapKeys)
{
object key;
if (reflection.TryParseEnumReference(keyString, out var @enum))
{
key = @enum;
}
else
{
key = keyString;
}
if (LayerMap.TryGetValue(key, out var mappedIndex))
{
if (mappedIndex != index)
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
continue;
}
_layerMapEnsurePrivate();
LayerMap[key] = index;
}
}
layer.Color = layerDatum.Color;
layer._rotation = layerDatum.Rotation;
layer._offset = layerDatum.Offset;
layer._scale = layerDatum.Scale;
layer.UpdateLocalMatrix();
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
layer.Visible = anyTextureAttempted && layerDatum.Visible;
}
public void LayerSetData(object layerKey, PrototypeLayerData data)
{
if (!LayerMapTryGet(layerKey, out var layer))
{
Logger.ErrorS(LogCategory, "Layer with key '{0}' does not exist, cannot set shader! Trace:\n{1}",
layerKey, Environment.StackTrace);
return;
}
LayerSetData(layer, data);
}
public void LayerSetShader(int layer, ShaderInstance? shader, string? prototype = null)
public void LayerSetShader(int layer, ShaderInstance? shader)
{
if (Layers.Count <= layer)
{
@@ -738,10 +664,9 @@ namespace Robust.Client.GameObjects
var theLayer = Layers[layer];
theLayer.Shader = shader;
theLayer.ShaderPrototype = prototype;
}
public void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null)
public void LayerSetShader(object layerKey, ShaderInstance shader)
{
if (!LayerMapTryGet(layerKey, out var layer))
{
@@ -750,7 +675,7 @@ namespace Robust.Client.GameObjects
return;
}
LayerSetShader(layer, shader, prototype);
LayerSetShader(layer, shader);
}
public void LayerSetShader(int layer, string shaderName)
@@ -759,12 +684,10 @@ namespace Robust.Client.GameObjects
{
Logger.ErrorS(LogCategory, "Shader prototype '{0}' does not exist. Trace:\n{1}", shaderName,
Environment.StackTrace);
LayerSetShader(layer, null, null);
return;
}
LayerSetShader(layer, prototype.Instance(), shaderName);
// This will set the shader to null if it does not exist.
LayerSetShader(layer, prototype?.Instance());
}
public void LayerSetShader(object layerKey, string shaderName)
@@ -1225,7 +1148,7 @@ namespace Robust.Client.GameObjects
return;
}
Layers[layer].Offset = layerOffset;
Layers[layer].SetOffset(layerOffset);
}
public void LayerSetOffset(object layerKey, Vector2 layerOffset)
@@ -1307,6 +1230,25 @@ namespace Robust.Client.GameObjects
RenderInternal(drawingHandle, eyeRotation, worldRotation, worldPosition, overrideDir);
}
private void CalcModelMatrix(int numDirs, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, out Matrix3 modelMatrix)
{
Angle angle;
if (_screenLock)
{
// Negate the eye rotation in the model matrix, so that later when the view matrix is applied the
// sprite will be locked upright to the screen
angle = new Angle(-eyeRotation.Theta);
}
else
{
angle = CalcRectWorldAngle(worldRotation + eyeRotation, numDirs) - eyeRotation;
}
var sWorldRotation = angle;
modelMatrix = Matrix3.CreateTransform(in worldPosition, in sWorldRotation);
}
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection)
{
// Reduce the angles to fix math shenanigans
@@ -1315,16 +1257,56 @@ namespace Robust.Client.GameObjects
if (worldRotation.Theta < 0)
worldRotation = new Angle(worldRotation.Theta + Math.Tau);
// worldRotation + eyeRotation should be the angle of the entity on-screen. If no-rot is enabled this is just set to zero.
// However, at some point later the eye-matrix is applied separately, so we subtract -eye rotation for now:
var entityMatrix = Matrix3.CreateTransform(worldPosition, NoRotation ? -eyeRotation : worldRotation);
// sprite matrix, WITHOUT offset.
// offset is applied after sprite numDirs snapping/rotation correction
// --> apply at same time as layer offset
var spriteMatrix = Matrix3.CreateTransform(Vector2.Zero, rotation, scale);
Matrix3.Multiply(ref LocalMatrix, ref entityMatrix, out var transform);
var angle = worldRotation + eyeRotation; // angle on-screen. Used to decide the direction of 4/8 directional RSIs
foreach (var layer in Layers)
{
layer.Render(drawingHandle, ref transform, angle, overrideDirection);
if (!layer.Visible)
{
continue;
}
var numDirs = GetLayerDirectionCount(layer);
var layerRotation = worldRotation + layer.Rotation;
var layerPosition = worldPosition + layerRotation.RotateVec(layer._offset + offset);
CalcModelMatrix(numDirs, eyeRotation, layerRotation, layerPosition, out var modelMatrix);
Matrix3.Multiply(ref spriteMatrix, ref modelMatrix, out var transformMatrix);
drawingHandle.SetTransform(in transformMatrix);
RenderLayer(drawingHandle, layer, eyeRotation, layerRotation, overrideDirection);
}
}
private void RenderLayer(DrawingHandleWorld drawingHandle, Layer layer, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection)
{
var texture = GetRenderTexture(layer, worldRotation + eyeRotation, overrideDirection);
if (layer.Shader != null)
{
drawingHandle.UseShader(layer.Shader);
}
var layerColor = color * layer.Color;
var position = -(Vector2)texture.Size / (2f * EyeManager.PixelsPerMeter);
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
var quad = Box2.FromDimensions(position, textureSize);
// TODO: Implement layer-specific rotation and scale.
// Apply these directly to the box.
// Oh and when you do update Layer.LocalToLayer so content doesn't break.
// handle.Modulate changes the color
// drawingHandle.SetTransform() is set above, turning the quad into local space vertices
drawingHandle.DrawTextureRectRegion(texture, quad, layerColor);
if (layer.Shader != null)
{
drawingHandle.UseShader(null);
}
}
@@ -1360,6 +1342,27 @@ namespace Robust.Client.GameObjects
};
}
private Texture GetRenderTexture(Layer layer, Angle worldRotation, Direction? overrideDirection)
{
var texture = layer.Texture;
if (layer.State.IsValid)
{
// Pull texture from RSI state instead.
var rsi = layer.RSI ?? BaseRSI;
if (rsi == null || !rsi.TryGetState(layer.State, out var state))
{
state = GetFallbackState(resourceCache);
}
var layerSpecificDir = layer.EffectiveDirection(state, worldRotation, overrideDirection);
texture = state.GetFrame(layerSpecificDir, layer.AnimationFrame);
}
texture ??= resourceCache.GetFallback<TextureResource>().Texture;
return texture;
}
public void FrameUpdate(float delta)
{
foreach (var t in Layers)
@@ -1414,14 +1417,12 @@ namespace Robust.Client.GameObjects
Visible = thestate.Visible;
DrawDepth = thestate.DrawDepth;
scale = thestate.Scale;
rotation = thestate.Rotation;
offset = thestate.Offset;
UpdateLocalMatrix();
Scale = thestate.Scale;
Rotation = thestate.Rotation;
Offset = thestate.Offset;
Color = thestate.Color;
RenderOrder = thestate.RenderOrder;
if (thestate.BaseRsiPath != null && BaseRSI != null)
{
if (resourceCache.TryGetResource<RSIResource>(TextureRoot / thestate.BaseRsiPath, out var res))
@@ -1437,8 +1438,43 @@ namespace Robust.Client.GameObjects
}
}
// Maybe optimize this to NOT fully clear the layers. (see LayerDatums setter function)
LayerDatums = thestate.Layers;
// Maybe optimize this to NOT full clear.
Layers.Clear();
for (var i = 0; i < thestate.Layers.Count; i++)
{
var netlayer = thestate.Layers[i];
var layer = new Layer(this)
{
// These are easy so do them here.
Scale = netlayer.Scale,
Rotation = netlayer.Rotation,
Visible = netlayer.Visible,
Color = netlayer.Color
};
Layers.Add(layer);
// Using the public API to handle errors.
// Probably slow as crap.
// Who am I kidding, DEFINITELY.
if (netlayer.Shader != null)
{
LayerSetShader(i, netlayer.Shader);
}
if (netlayer.RsiPath != null)
{
LayerSetRSI(i, netlayer.RsiPath);
}
if (netlayer.TexturePath != null)
{
LayerSetTexture(i, netlayer.TexturePath);
}
else if (netlayer.State != null)
{
LayerSetState(i, netlayer.State);
}
}
}
private void QueueUpdateIsInert()
@@ -1517,61 +1553,35 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc/>
public Box2Rotated CalculateRotatedBoundingBox(Vector2 worldPosition, Angle worldRotation, IEye? eye = null)
public Box2 CalculateBoundingBox(Vector2 worldPos)
{
// fast check for empty sprites
if (!Visible || Layers.Count == 0)
{
return new Box2Rotated(new Box2(worldPosition, worldPosition), Angle.Zero, worldPosition);
}
// We need to modify world rotation so that it lies between 0 and 2pi.
// This matters for 4 or 8 directional sprites deciding which quadrant (octant?) they lie in.
// the 0->2pi convention is set by the sprite-rendering code that selects the layers.
// See RenderInternal().
worldRotation = worldRotation.Reduced();
if (worldRotation.Theta < 0)
worldRotation = new Angle(worldRotation.Theta + Math.Tau);
eye ??= eyeManager.CurrentEye;
// Need relative angle on screen for determining the sprite rsi direction.
Angle relativeRotation = NoRotation
? Angle.Zero
: worldRotation + eye.Rotation;
if (Layers.Count == 0)
return new Box2(worldPos, worldPos);
// we need to calculate bounding box taking into account all nested layers
// because layers can have offsets, scale or rotation, we need to calculate a new BB
// based on lowest bottomLeft and highest topRight points from all layers
var box = Layers[0].CalculateBoundingBox(relativeRotation);
// because layers can have offsets, scale or rotation we need to calculate a new BB
// based on lowest bottomLeft and hightest topRight points from all layers
var box = Layers[0].CalculateBoundingBox();
for (int i = 1; i < Layers.Count; i++)
{
var layer = Layers[i];
if (!layer.Visible) continue;
var layerBB = layer.CalculateBoundingBox(relativeRotation);
var layerBB = layer.CalculateBoundingBox();
box = box.Union(layerBB);
}
// Next, what we do is take the box2 and apply the sprite's transform, and then the entity's transform. We
// could do this via Matrix3.TransformBox, but that only yields bounding boxes. So instead we manually
// transform our box by the combination of these matrices:
// apply sprite transformations and calculate sprite bounding box
// we can optimize it a bit, if sprite doesn't have rotation
var spriteBox = box.Scale(Scale);
var spriteHasRotation = !Rotation.EqualsApprox(Angle.Zero);
var spriteBB = spriteHasRotation ?
new Box2Rotated(spriteBox, Rotation).CalcBoundingBox() : spriteBox;
if (Scale != Vector2.One)
box = box.Scale(Scale);
var adjustedOffset = NoRotation
? (-eye.Rotation).RotateVec(Offset)
: worldRotation.RotateVec(Offset);
Vector2 position = adjustedOffset + worldPosition;
Angle finalRotation = NoRotation
? Rotation - eye.Rotation
: Rotation + worldRotation;
return new Box2Rotated(box.Translated(position), finalRotation, position);
// move it all to world transform system (with sprite offset)
var worldBB = spriteBB.Translated(Offset + worldPos);
return worldBB;
}
internal void UpdateBounds()
@@ -1605,11 +1615,10 @@ namespace Robust.Client.GameObjects
Flip = 3,
}
public sealed class Layer : ISpriteLayer, ISerializationHooks
public sealed class Layer : ISpriteLayer
{
[ViewVariables] private readonly SpriteComponent _parent;
[ViewVariables] public string? ShaderPrototype;
[ViewVariables] public ShaderInstance? Shader;
[ViewVariables] public Texture? Texture;
@@ -1619,37 +1628,11 @@ namespace Robust.Client.GameObjects
[ViewVariables] public float AnimationTime;
[ViewVariables] public int AnimationFrame;
public Matrix3 LocalMatrix = Matrix3.Identity;
[ViewVariables(VVAccess.ReadWrite)]
public Vector2 Scale { get; set; } = Vector2.One;
[ViewVariables(VVAccess.ReadWrite)]
public Vector2 Scale
{
get => _scale;
set
{
if (_scale.EqualsApprox(value)) return;
_scale = value;
UpdateLocalMatrix();
_parent.UpdateBounds();
}
}
internal Vector2 _scale = Vector2.One;
[ViewVariables(VVAccess.ReadWrite)]
public Angle Rotation
{
get => _rotation;
set
{
if (_rotation.EqualsApprox(value)) return;
_rotation = value;
UpdateLocalMatrix();
_parent.UpdateBounds();
}
}
internal Angle _rotation = Angle.Zero;
public Angle Rotation { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public bool Visible = true;
@@ -1669,7 +1652,6 @@ namespace Robust.Client.GameObjects
if (_offset.EqualsApprox(value)) return;
_offset = value;
UpdateLocalMatrix();
_parent.UpdateBounds();
}
}
@@ -1693,7 +1675,6 @@ namespace Robust.Client.GameObjects
if (toClone.Shader != null)
{
Shader = toClone.Shader.Mutable ? toClone.Shader.Duplicate() : toClone.Shader;
ShaderPrototype = toClone.ShaderPrototype;
}
Texture = toClone.Texture;
RSI = toClone.RSI;
@@ -1701,26 +1682,14 @@ namespace Robust.Client.GameObjects
AnimationTimeLeft = toClone.AnimationTimeLeft;
AnimationTime = toClone.AnimationTime;
AnimationFrame = toClone.AnimationFrame;
_scale = toClone.Scale;
_rotation = toClone.Rotation;
_offset = toClone.Offset;
UpdateLocalMatrix();
Scale = toClone.Scale;
Rotation = toClone.Rotation;
Visible = toClone.Visible;
Color = toClone.Color;
DirOffset = toClone.DirOffset;
AutoAnimated = toClone.AutoAnimated;
}
void ISerializationHooks.AfterDeserialization()
{
UpdateLocalMatrix();
}
internal void UpdateLocalMatrix()
{
LocalMatrix = Matrix3.CreateTransform(in _offset, in _rotation, in _scale);
}
RSI? ISpriteLayer.Rsi { get => RSI; set => SetRsi(value); }
RSI.StateId ISpriteLayer.RsiState { get => State; set => SetState(value); }
Texture? ISpriteLayer.Texture { get => Texture; set => SetTexture(value); }
@@ -1732,7 +1701,7 @@ namespace Robust.Client.GameObjects
Color = Color,
Rotation = Rotation,
Scale = Scale,
Shader = ShaderPrototype,
//todo Shader = Shader,
State = State.Name,
Visible = Visible,
RsiPath = RSI?.Path?.ToString(),
@@ -1761,7 +1730,7 @@ namespace Robust.Client.GameObjects
set => SetAutoAnimated(value);
}
public RSIDirection EffectiveDirection(Angle worldRotation)
public RSI.State.Direction EffectiveDirection(Angle worldRotation)
{
if (State == default)
{
@@ -1782,16 +1751,22 @@ namespace Robust.Client.GameObjects
return default;
}
public RSIDirection EffectiveDirection(RSI.State state, Angle worldRotation,
public Vector2 LocalToLayer(Vector2 localPos)
{
// TODO: scale & rotation for layers is currently unimplemented.
return localPos;
}
public RSI.State.Direction EffectiveDirection(RSI.State state, Angle worldRotation,
Direction? overrideDirection)
{
if (state.Directions == RSI.State.DirectionType.Dir1)
{
return RSIDirection.South;
return RSI.State.Direction.South;
}
else
{
RSIDirection dir;
RSI.State.Direction dir;
if (overrideDirection != null)
{
dir = overrideDirection.Value.Convert(state.Directions);
@@ -1874,7 +1849,7 @@ namespace Robust.Client.GameObjects
}
else
{
Logger.ErrorS(LogCategory, "State '{0}' does not exist in set RSI ({1}). Trace:\n{2}", State, rsi?.Path?.ToString() ?? "null",
Logger.ErrorS(LogCategory, "State '{0}' does not exist in set RSI. Trace:\n{1}", State,
Environment.StackTrace);
Texture = null;
}
@@ -1923,6 +1898,11 @@ namespace Robust.Client.GameObjects
_parent.QueueUpdateIsInert();
}
public void SetOffset(Vector2 offset)
{
Offset = offset;
}
/// <inheritdoc/>
public Vector2i PixelSize
{
@@ -1943,121 +1923,10 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc/>
public Box2 CalculateBoundingBox(Angle angle)
public Box2 CalculateBoundingBox()
{
// Other than some special cases for simple layers, this will basically just apply the matrix obtained
// via GetLayerDrawMatrix() to this layer's bounding box.
var rsiState = GetActualState();
var dir = (rsiState == null || rsiState.Directions == RSI.State.DirectionType.Dir1)
? RSIDirection.South
: angle.ToRsiDirection(rsiState.Directions);
// special case for simple layers. The vast majority of layers are like this.
if (_rotation == Angle.Zero)
{
var textureSize = PixelSize / EyeManager.PixelsPerMeter;
// this switch block is basically an explicit version of the `rsiDirectionMatrix` in `GetLayerDrawMatrix()`.
var box = dir switch
{
// No rotation:
RSIDirection.South or RSIDirection.North => Box2.CenteredAround(Offset, textureSize),
// rotate 90 degrees:
RSIDirection.East or RSIDirection.West => Box2.CenteredAround(Offset, (textureSize.Y, textureSize.X)),
// rotated 45 degrees (any 45 degree rotated rectangle has a square bounding box with sides of length (x+y)/sqrt(2) )
_ => Box2.CenteredAround(Offset, Vector2.One * (textureSize.X + textureSize.Y) / MathF.Sqrt(2))
};
return _scale == Vector2.One ? box : box.Scale(_scale);
}
// Welp we have some non-zero _rotation, so lets just apply the generalized layer transform and get the bounding box from where;
GetLayerDrawMatrix(dir, out var layerDrawMatrix);
return layerDrawMatrix.TransformBox(Box2.CentredAroundZero(PixelSize / EyeManager.PixelsPerMeter));
}
internal RSI.State? GetActualState()
{
if (!State.IsValid)
return null;
// Pull texture from RSI state
var rsi = RSI ?? _parent.BaseRSI;
if (rsi == null || !rsi.TryGetState(State, out var rsiState))
{
rsiState = GetFallbackState(_parent.resourceCache);
}
return rsiState;
}
/// <summary>
/// Given the apparent rotation of an entity on screen (world + eye rotation), get layer's matrix for drawing &
/// relevant RSI direction.
/// </summary>
public void GetLayerDrawMatrix(RSIDirection dir, out Matrix3 layerDrawMatrix)
{
if (_parent.NoRotation)
layerDrawMatrix = LocalMatrix;
else
{
var rsiDirectionMatrix = Matrix3.CreateTransform(Vector2.Zero, -dir.Convert().ToAngle());
Matrix3.Multiply(ref rsiDirectionMatrix, ref LocalMatrix, out layerDrawMatrix);
}
}
internal void Render(DrawingHandleWorld drawingHandle, ref Matrix3 spriteMatrix, Angle angle, Direction? overrideDirection)
{
if (!Visible)
return;
var rsiState = GetActualState();
var dir = (rsiState == null || rsiState.Directions == RSI.State.DirectionType.Dir1)
? RSIDirection.South
: angle.ToRsiDirection(rsiState.Directions);
// Set the drawing transform for this layer
GetLayerDrawMatrix(dir, out var layerMatrix);
Matrix3.Multiply(ref layerMatrix, ref spriteMatrix, out var transformMatrix);
drawingHandle.SetTransform(in transformMatrix);
// The direction used to draw the sprite can differ from the one that the angle would naively suggest,
// due to direction overrides or offsets.
if (overrideDirection != null && rsiState != null)
dir = overrideDirection.Value.Convert(rsiState.Directions);
dir = dir.OffsetRsiDir(DirOffset);
// Get the correct directional texture from the state, and draw it!
var texture = GetRenderTexture(rsiState, dir);
RenderTexture(drawingHandle, texture);
}
private void RenderTexture(DrawingHandleWorld drawingHandle, Texture texture)
{
if (Shader != null)
drawingHandle.UseShader(Shader);
var layerColor = _parent.color * Color;
var position = -(Vector2)texture.Size / (2f * EyeManager.PixelsPerMeter);
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
var quad = Box2.FromDimensions(position, textureSize);
drawingHandle.DrawTextureRectRegion(texture, quad, layerColor);
if (Shader != null)
drawingHandle.UseShader(null);
}
private Texture GetRenderTexture(RSI.State? state, RSIDirection dir)
{
if (state == null)
return Texture ?? _parent.resourceCache.GetFallback<TextureResource>().Texture;
return state.GetFrame(dir, AnimationFrame);
// TODO: scale & rotation for layers is currently unimplemented.
return Box2.CenteredAround(Offset, PixelSize / EyeManager.PixelsPerMeter);
}
}
@@ -2165,7 +2034,6 @@ namespace Robust.Client.GameObjects
return results;
}
[Obsolete("Use SpriteSystem")]
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
{
var icon = IconComponent.GetPrototypeIcon(prototype, resourceCache);

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
@@ -7,7 +6,7 @@ using Robust.Shared.GameStates;
namespace Robust.Client.GameObjects
{
[UsedImplicitly]
public sealed class AppearanceSystem : SharedAppearanceSystem
internal sealed class AppearanceSystem : SharedAppearanceSystem
{
private readonly Queue<ClientAppearanceComponent> _queuedUpdates = new();
@@ -51,33 +50,10 @@ namespace Robust.Client.GameObjects
if (!stateDiff) return;
component.AppearanceData = CloneAppearanceData(actualState.Data);
component.AppearanceData = actualState.Data;
MarkDirty(component);
}
/// <summary>
/// Take in an appearance data dictionary and attempt to clone it.
/// </summary>
/// <remarks>
/// As some appearance data values are not simple value-type objects, this is not just a shallow clone.
/// </remarks>
private Dictionary<object, object> CloneAppearanceData(Dictionary<object, object> data)
{
Dictionary<object, object> newDict = new(data.Count);
foreach (var (key, value) in data)
{
if (value.GetType().IsValueType)
newDict[key] = value;
else if (value is ICloneable cloneable)
newDict[key] = cloneable.Clone();
else
throw new NotSupportedException("Invalid object in appearance data dictionary. Appearance data must be cloneable");
}
return newDict;
}
public override void MarkDirty(AppearanceComponent component)
{
if (component.AppearanceDirty)
@@ -131,7 +107,7 @@ namespace Robust.Client.GameObjects
[ByRefEvent]
public struct AppearanceChangeEvent
{
public AppearanceComponent Component;
public IReadOnlyDictionary<object, object> AppearanceData;
public AppearanceComponent Component = default!;
public IReadOnlyDictionary<object, object> AppearanceData = default!;
}
}

View File

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

View File

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

View File

@@ -323,7 +323,7 @@ namespace Robust.Client.GameObjects
{
private readonly IPlayerManager _playerManager;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _unshadedShader;
private readonly EffectSystem _owner;
@@ -377,9 +377,6 @@ namespace Robust.Client.GameObjects
(attachedXform?.Coordinates ?? effect.Coordinates)
.Offset(effect.AttachedOffset);
// If we've never seen the entity before then can't resolve coordinates.
if (!coordinates.IsValid(_entityManager)) continue;
// ???
var rotation = attachedXform?.WorldRotation ?? _entityManager.GetComponent<TransformComponent>(coordinates.EntityId).WorldRotation;

View File

@@ -1,19 +0,0 @@
using Robust.Client.Graphics;
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects;
internal class GridRenderingSystem : EntitySystem
{
private readonly IClydeInternal _clyde;
public GridRenderingSystem(IClydeInternal clyde)
{
_clyde = clyde;
}
public override void Initialize()
{
_clyde.RegisterGridEcsEvents();
}
}

View File

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

View File

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

View File

@@ -1,84 +0,0 @@
using System;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects;
public sealed partial class SpriteSystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Pure]
public Texture Frame0(SpriteSpecifier specifier)
{
return RsiStateLike(specifier).Default;
}
[Pure]
public IRsiStateLike RsiStateLike(SpriteSpecifier specifier)
{
switch (specifier)
{
case SpriteSpecifier.Texture tex:
return tex.GetTexture(_resourceCache);
case SpriteSpecifier.Rsi rsi:
return GetState(rsi);
case SpriteSpecifier.EntityPrototype prototypeIcon:
if (!_proto.TryIndex<EntityPrototype>(prototypeIcon.EntityPrototypeId, out var prototype))
{
Logger.Error("Failed to load PrototypeIcon {0}", prototypeIcon.EntityPrototypeId);
return SpriteComponent.GetFallbackState(_resourceCache);
}
return SpriteComponent.GetPrototypeIcon(prototype, _resourceCache);
default:
throw new NotSupportedException();
}
}
[Pure]
public IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
{
var icon = IconComponent.GetPrototypeIcon(prototype, _resourceCache);
if (icon != null) return icon;
if (!prototype.Components.ContainsKey("Sprite"))
{
return SpriteComponent.GetFallbackState(resourceCache);
}
var dummy = Spawn(prototype.ID, MapCoordinates.Nullspace);
var spriteComponent = EnsureComp<SpriteComponent>(dummy);
var result = spriteComponent.Icon ?? SpriteComponent.GetFallbackState(resourceCache);
Del(dummy);
return result;
}
[Pure]
public RSI.State GetState(SpriteSpecifier.Rsi rsiSpecifier)
{
if (_resourceCache.TryGetResource<RSIResource>(
SharedSpriteComponent.TextureRoot / rsiSpecifier.RsiPath,
out var theRsi) &&
theRsi.RSI.TryGetState(rsiSpecifier.RsiState, out var state))
{
return state;
}
Logger.Error("Failed to load RSI {0}", rsiSpecifier.RsiPath);
return SpriteComponent.GetFallbackState(_resourceCache);
}
}

View File

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

View File

@@ -45,6 +45,7 @@ namespace Robust.Client.GameStates
[Dependency] private readonly IComponentFactory _compFactory = default!;
[Dependency] private readonly IClientEntityManagerInternal _entities = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IPlayerManager _players = default!;
[Dependency] private readonly IClientNetManager _network = default!;
[Dependency] private readonly IBaseClient _client = default!;
@@ -331,6 +332,8 @@ namespace Robust.Client.GameStates
}
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: !IsPredictionEnabled);
_lookup.Update();
}
private void ResetPredictedEntities(GameTick curTick)
@@ -421,6 +424,7 @@ namespace Robust.Client.GameStates
var createdEntities = ApplyEntityStates(curState.EntityStates.Span, curState.EntityDeletions.Span,
nextState != null ? nextState.EntityStates.Span : default);
_players.ApplyPlayerStates(curState.PlayerStates.Value ?? Array.Empty<PlayerState>());
_mapManager.ApplyGameStatePost(curState.MapData);
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState));
return createdEntities;

View File

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

View File

@@ -1,5 +1,7 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -16,8 +18,8 @@ namespace Robust.Client.Graphics.Clyde
private readonly Dictionary<GridId, Dictionary<Vector2i, MapChunkData>> _mapChunkData =
new();
private int _verticesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
private int _indicesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
private int _verticesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
private int _indicesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
private void _drawGrids(Viewport viewport, Box2Rotated worldBounds, IEye eye)
{
@@ -76,7 +78,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
private void _updateChunkMesh(IMapGrid grid, MapChunk chunk)
private void _updateChunkMesh(IMapGrid grid, IMapChunk chunk)
{
var data = _mapChunkData[grid.Index];
@@ -89,41 +91,25 @@ namespace Robust.Client.Graphics.Clyde
Span<Vertex2D> vertexBuffer = stackalloc Vertex2D[_verticesPerChunk(chunk)];
var i = 0;
var cSz = grid.ChunkSize;
var cScaled = chunk.Indices * cSz;
for (ushort x = 0; x < cSz; x++)
foreach (var tile in chunk)
{
for (ushort y = 0; y < cSz; y++)
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile.Tile);
if (regionMaybe == null)
{
var tile = chunk.GetTile(x, y);
if (tile.IsEmpty)
continue;
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile);
Box2 region;
if (regionMaybe == null || regionMaybe.Length <= tile.Variant)
{
region = _tileDefinitionManager.ErrorTileRegion;
}
else
{
region = regionMaybe[tile.Variant];
}
var gx = x + cScaled.X;
var gy = y + cScaled.Y;
var vIdx = i * 4;
vertexBuffer[vIdx + 0] = new Vertex2D(gx, gy, region.Left, region.Bottom);
vertexBuffer[vIdx + 1] = new Vertex2D(gx + 1, gy, region.Right, region.Bottom);
vertexBuffer[vIdx + 2] = new Vertex2D(gx + 1, gy + 1, region.Right, region.Top);
vertexBuffer[vIdx + 3] = new Vertex2D(gx, gy + 1, region.Left, region.Top);
var nIdx = i * GetQuadBatchIndexCount();
var tIdx = (ushort)(i * 4);
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
i += 1;
continue;
}
var region = regionMaybe.Value;
var vIdx = i * 4;
vertexBuffer[vIdx + 0] = new Vertex2D(tile.X, tile.Y, region.Left, region.Bottom);
vertexBuffer[vIdx + 1] = new Vertex2D(tile.X + 1, tile.Y, region.Right, region.Bottom);
vertexBuffer[vIdx + 2] = new Vertex2D(tile.X + 1, tile.Y + 1, region.Right, region.Top);
vertexBuffer[vIdx + 3] = new Vertex2D(tile.X, tile.Y + 1, region.Left, region.Top);
var nIdx = i * GetQuadBatchIndexCount();
var tIdx = (ushort) (i * 4);
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
i += 1;
}
GL.BindVertexArray(datum.VAO);
@@ -136,7 +122,7 @@ namespace Robust.Client.Graphics.Clyde
datum.TileCount = i;
}
private MapChunkData _initChunkBuffers(IMapGrid grid, MapChunk chunk)
private MapChunkData _initChunkBuffers(IMapGrid grid, IMapChunk chunk)
{
var vao = GenVertexArray();
BindVertexArray(vao);
@@ -173,7 +159,7 @@ namespace Robust.Client.Graphics.Clyde
return datum;
}
private bool _isChunkDirty(IMapGrid grid, MapChunk chunk)
private bool _isChunkDirty(IMapGrid grid, IMapChunk chunk)
{
var data = _mapChunkData[grid.Index];
return !data.TryGetValue(chunk.Indices, out var datum) || datum.Dirty;
@@ -189,7 +175,7 @@ namespace Robust.Client.Graphics.Clyde
// Don't need to set it if we don't have an entry since lack of an entry is treated as dirty.
}
private void _updateOnGridModified(GridModifiedEvent args)
private void _updateOnGridModified(object? sender, GridChangedEventArgs args)
{
foreach (var (pos, _) in args.Modified)
{
@@ -199,23 +185,21 @@ namespace Robust.Client.Graphics.Clyde
}
}
private void _updateTileMapOnUpdate(TileChangedEvent args)
private void _updateTileMapOnUpdate(object? sender, TileChangedEventArgs args)
{
var grid = _mapManager.GetGrid(args.NewTile.GridIndex);
var chunk = grid.GridTileToChunkIndices(new Vector2i(args.NewTile.X, args.NewTile.Y));
_setChunkDirty(grid, chunk);
}
private void _updateOnGridCreated(GridStartupEvent ev)
private void _updateOnGridCreated(MapId mapId, GridId gridId)
{
var gridId = ev.GridId;
Logger.DebugS("grid", $"Adding {gridId} to grid renderer");
_mapChunkData.Add(gridId, new Dictionary<Vector2i, MapChunkData>());
}
private void _updateOnGridRemoved(GridRemovalEvent ev)
private void _updateOnGridRemoved(MapId mapId, GridId gridId)
{
var gridId = ev.GridId;
Logger.DebugS("grid", $"Removing {gridId} from grid renderer");
var data = _mapChunkData[gridId];

View File

@@ -24,17 +24,11 @@ namespace Robust.Client.Graphics.Clyde
{
public ClydeDebugLayers DebugLayers { get; set; }
private readonly RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRotation, Box2 spriteScreenBB)>
private readonly RefList<(SpriteComponent sprite, Matrix3 worldMatrix, Angle worldRotation, float yWorldPos)>
_drawingSpriteList
=
new();
// TODO allow this scale to be passed with PostShader as variable
/// <summary>
/// Some shaders that enlarge the final sprite, like emission or highlight effects, need to use a slightly larger render target.
/// </summary>
public static float PostShadeScale = 1.25f;
public void Render()
{
CheckTransferringScreenshots();
@@ -223,8 +217,9 @@ namespace Robust.Client.Graphics.Clyde
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldAABB, worldBounds);
var screenSize = viewport.Size;
eye.GetViewMatrix(out var eyeMatrix, eye.Scale);
ProcessSpriteEntities(mapId, viewport, eye, worldBounds, _drawingSpriteList);
ProcessSpriteEntities(mapId, eyeMatrix, worldBounds, _drawingSpriteList);
var worldOverlays = new List<Overlay>();
@@ -281,12 +276,30 @@ namespace Robust.Client.Graphics.Clyde
break;
}
var matrix = entry.worldMatrix;
var worldPosition = new Vector2(matrix.R0C2, matrix.R1C2);
RenderTexture? entityPostRenderTarget = null;
Vector2i roundedPos = default;
if (entry.sprite.PostShader != null)
{
// get the size of the sprite on screen, scaled slightly to allow for shaders that increase the final sprite size.
var screenSpriteSize = (Vector2i) (entry.spriteScreenBB.Size * PostShadeScale).Rounded();
// calculate world bounding box
var spriteBB = entry.sprite.CalculateBoundingBox(worldPosition);
var spriteLB = spriteBB.BottomLeft;
var spriteRT = spriteBB.TopRight;
// finally we can calculate screen bounding in pixels
var screenLB = viewport.WorldToLocal(spriteLB);
var screenRT = viewport.WorldToLocal(spriteRT);
// we need to scale RT a for effects like emission or highlight
// scale can be passed with PostShader as variable in future
var postShadeScale = 1.25f;
var screenSpriteSize = (Vector2i) ((screenRT - screenLB) * postShadeScale).Rounded();
// Rotate the vector by the eye angle, otherwise the bounding box will be incorrect
screenSpriteSize = (Vector2i) eye.Rotation.RotateVec(screenSpriteSize).Rounded();
screenSpriteSize.Y = -screenSpriteSize.Y;
// I'm not 100% sure why it works, but without it post-shader
// can be lower or upper by 1px than original sprite depending on sprite rotation or scale
@@ -309,14 +322,16 @@ namespace Robust.Client.Graphics.Clyde
// Calculate viewport so that the entity thinks it's drawing to the same position,
// which is necessary for light application,
// but it's ACTUALLY drawing into the center of the render target.
roundedPos = (Vector2i) entry.spriteScreenBB.Center;
var flippedPos = new Vector2i(roundedPos.X, screenSize.Y - roundedPos.Y);
var spritePos = spriteBB.Center;
var screenPos = viewport.WorldToLocal(spritePos);
var (roundedX, roundedY) = roundedPos = (Vector2i) screenPos;
var flippedPos = new Vector2i(roundedX, screenSize.Y - roundedY);
flippedPos -= entityPostRenderTarget.Size / 2;
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, screenSize));
}
}
entry.sprite.Render(_renderHandle.DrawingHandleWorld, eye.Rotation, in entry.worldRotation, in entry.worldPos);
entry.sprite.Render(_renderHandle.DrawingHandleWorld, eye.Rotation, in entry.worldRotation, in worldPosition);
if (entry.sprite.PostShader != null && entityPostRenderTarget != null)
{
@@ -354,26 +369,17 @@ namespace Robust.Client.Graphics.Clyde
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void ProcessSpriteEntities(MapId map, Viewport view, IEye eye, Box2Rotated worldBounds,
RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRot, Box2 spriteScreenBB)> list)
private void ProcessSpriteEntities(MapId map, Matrix3 eyeMatrix, Box2Rotated worldBounds,
RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> list)
{
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
// Construct a matrix equivalent for Viewport.WorldToLocal()
eye.GetViewMatrix(out var viewMatrix, view.RenderScale);
var uiProjmatrix = Matrix3.Identity;
uiProjmatrix.R0C0 = EyeManager.PixelsPerMeter;
uiProjmatrix.R1C1 = -EyeManager.PixelsPerMeter;
uiProjmatrix.R0C2 = view.Size.X / 2f;
uiProjmatrix.R1C2 = view.Size.Y / 2f;
var worldToLocal = viewMatrix * uiProjmatrix;
foreach (var comp in _entitySystemManager.GetEntitySystem<RenderingTreeSystem>().GetRenderTrees(map, worldBounds))
{
var bounds = xforms.GetComponent(comp.Owner).InvWorldMatrix.TransformBox(worldBounds);
comp.SpriteTree.QueryAabb(ref list, (
ref RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRot, Box2 spriteScreenBB)> state,
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
in SpriteComponent value) =>
{
var entity = value.Owner;
@@ -381,10 +387,12 @@ namespace Robust.Client.Graphics.Clyde
ref var entry = ref state.AllocAdd();
entry.sprite = value;
(entry.worldPos, entry.worldRot) = transform.GetWorldPositionRotation();
var spriteWorldBB = value.CalculateRotatedBoundingBox(entry.worldPos, entry.worldRot, eye);
entry.spriteScreenBB = worldToLocal.TransformBox(spriteWorldBB);
Vector2 worldPos;
(worldPos, entry.worldRot, entry.matrix) = transform.GetWorldPositionRotationMatrix();
var eyePos = eyeMatrix.Transform(worldPos);
// Didn't use the bounds from the query as that has to be re-calculated (and is probably more expensive than this).
var bounds = value.CalculateBoundingBox(eyePos);
entry.yWorldPos = eyePos.Y - bounds.Extents.Y;
return true;
}, bounds, true);

View File

@@ -514,14 +514,14 @@ namespace Robust.Client.Graphics.Clyde
var enlargedBounds = worldAABB.Enlarged(renderingTreeSystem.MaxLightRadius);
// Use worldbounds for this one as we only care if the light intersects our actual bounds
var state = (this, worldAABB, count: 0, shadowCastingCount: 0);
var state = (this, worldAABB, count: 0);
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
foreach (var comp in renderingTreeSystem.GetRenderTrees(map, enlargedBounds))
{
var bounds = xforms.GetComponent(comp.Owner).InvWorldMatrix.TransformBox(worldBounds);
comp.LightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldAABB, int count, int shadowCastingCount) state, in PointLightComponent light) =>
comp.LightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldAABB, int count) state, in PointLightComponent light) =>
{
if (state.count >= LightsToRenderListSize)
{
@@ -543,9 +543,6 @@ namespace Robust.Client.Graphics.Clyde
return true;
}
// If the light is a shadow casting light, keep a separate track of that
if (light.CastShadows) state.shadowCastingCount++;
float distanceSquared = (state.worldAABB.Center - lightPos).LengthSquared;
state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared);
@@ -553,29 +550,17 @@ namespace Robust.Client.Graphics.Clyde
}, bounds);
}
if (state.shadowCastingCount > _maxLightsPerScene)
if (state.count > _maxLightsPerScene)
{
// There are too many lights casting shadows to fit in the scene.
// There are too many lights to fit in the scene.
// This check must occur before occluder expansion, or else bad things happen.
// First, partition the array based on whether the lights are shadow casting or not
// (non shadow casting lights should be the first partition, shadow casting lights the second)
// Sort lights by distance.
Array.Sort(_lightsToRenderList, 0, state.count, Comparer<(PointLightComponent light, Vector2 pos, float distanceSquared)>.Create((x, y) =>
{
if (x.light.CastShadows && !y.light.CastShadows) return 1;
else if (!x.light.CastShadows && y.light.CastShadows) return -1;
else return 0;
}));
// Next, sort just the shadow casting lights by distance.
Array.Sort(_lightsToRenderList, state.count - state.shadowCastingCount, state.shadowCastingCount, Comparer<(PointLightComponent light, Vector2 pos, float distanceSquared)>.Create((x, y) =>
{
return x.distanceSquared.CompareTo(y.distanceSquared);
}));
// Then effectively delete the furthest lights, by setting the end of the array to exclude N
// number of shadow casting lights (where N is the number above the max number per scene.)
state.count -= state.shadowCastingCount - _maxLightsPerScene;
// Then effectively delete the furthest lights.
state.count = _maxLightsPerScene;
}
// When culling occluders later, we can't just remove any occluders outside the worldBounds.
@@ -861,6 +846,7 @@ namespace Robust.Client.Graphics.Clyde
// 3D geometry used during depth projection.
// 2D mask geometry used to apply wall bleed.
// TODO: This code probably does not work correctly with rotated camera.
// TODO: Yes this function throws and index exception if you reach maxOccluders.
const int maxOccluders = 2048;

View File

@@ -994,9 +994,9 @@ namespace Robust.Client.Graphics.Clyde
private sealed class SpriteDrawingOrderComparer : IComparer<int>
{
private readonly RefList<(SpriteComponent, Vector2, Angle, Box2)> _drawList;
private readonly RefList<(SpriteComponent, Matrix3, Angle, float)> _drawList;
public SpriteDrawingOrderComparer(RefList<(SpriteComponent, Vector2, Angle, Box2)> drawList)
public SpriteDrawingOrderComparer(RefList<(SpriteComponent, Matrix3, Angle, float)> drawList)
{
_drawList = drawList;
}
@@ -1019,8 +1019,7 @@ namespace Robust.Client.Graphics.Clyde
return cmp;
}
// compare the top of the sprite's BB for y-sorting. Because screen coordinates are flipped, the "top" of the BB is actually the "bottom".
cmp = _drawList[x].Item4.Top.CompareTo(_drawList[y].Item4.Top);
cmp = _drawList[y].Item4.CompareTo(_drawList[x].Item4);
if (cmp != 0)
{

View File

@@ -437,13 +437,6 @@ namespace Robust.Client.Graphics.Clyde
_windowing!.WindowSetVisible(reg, visible);
}
public void RunOnWindowThread(Action a)
{
DebugTools.AssertNotNull(_windowing);
_windowing!.RunOnWindowThread(a);
}
private abstract class WindowReg
{
public bool IsDisposed;

View File

@@ -22,7 +22,7 @@ namespace Robust.Client.Graphics.Clyde
/// <summary>
/// Responsible for most things rendering on OpenGL mode.
/// </summary>
internal sealed partial class Clyde : IClydeInternal, IPostInjectInit, IEntityEventSubscriber
internal sealed partial class Clyde : IClydeInternal, IPostInjectInit
{
[Dependency] private readonly IClydeTileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
@@ -134,19 +134,16 @@ namespace Robust.Client.Graphics.Clyde
public void PostInject()
{
_mapManager.TileChanged += _updateTileMapOnUpdate;
_mapManager.OnGridCreated += _updateOnGridCreated;
_mapManager.OnGridRemoved += _updateOnGridRemoved;
_mapManager.GridChanged += _updateOnGridModified;
// This cvar does not modify the actual GL version requested or anything,
// it overrides the version we detect to detect GL features.
RegisterBlockCVars();
}
public void RegisterGridEcsEvents()
{
_entityManager.EventBus.SubscribeEvent<TileChangedEvent>(EventSource.Local, this, _updateTileMapOnUpdate);
_entityManager.EventBus.SubscribeEvent<GridStartupEvent>(EventSource.Local, this, _updateOnGridCreated);
_entityManager.EventBus.SubscribeEvent<GridRemovalEvent>(EventSource.Local, this, _updateOnGridRemoved);
_entityManager.EventBus.SubscribeEvent<GridModifiedEvent>(EventSource.Local, this, _updateOnGridModified);
}
private void GLInitBindings(bool gles)
{
_glBindingsContext = _glContext!.BindingsContext;

View File

@@ -76,11 +76,6 @@ namespace Robust.Client.Graphics.Clyde
return null;
}
public void RegisterGridEcsEvents()
{
// Nada.
}
public void SetWindowTitle(string title)
{
// Nada.
@@ -251,11 +246,6 @@ namespace Robust.Client.Graphics.Clyde
// Nada.
}
public void RunOnWindowThread(Action action)
{
action();
}
private sealed class DummyCursor : ICursor
{
public void Dispose()

View File

@@ -118,10 +118,6 @@ namespace Robust.Client.Graphics.Clyde
case CmdWinCursorSet cmd:
WinThreadWinCursorSet(cmd);
break;
case CmdRunAction cmd:
cmd.Action();
break;
}
}
@@ -173,11 +169,6 @@ namespace Robust.Client.Graphics.Clyde
}
}
public void RunOnWindowThread(Action action)
{
SendCmd(new CmdRunAction(action));
}
private abstract record CmdBase;
private sealed record CmdTerminate : CmdBase;
@@ -254,10 +245,6 @@ namespace Robust.Client.Graphics.Clyde
private sealed record CmdCursorDestroy(
ClydeHandle Cursor
) : CmdBase;
private sealed record CmdRunAction(
Action Action
) : CmdBase;
}
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Robust.Client.Input;
using Robust.Shared.Maths;
using SixLabors.ImageSharp;
@@ -59,9 +58,6 @@ namespace Robust.Client.Graphics.Clyde
void GLMakeContextCurrent(WindowReg? reg);
void GLSwapInterval(int interval);
unsafe void* GLGetProcAddress(string procName);
// Misc
void RunOnWindowThread(Action a);
}
}
}

View File

@@ -9,7 +9,6 @@ using Robust.Shared.Utility;
using SharpFont;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using TerraFX.Interop.Windows;
namespace Robust.Client.Graphics
{
@@ -59,14 +58,6 @@ namespace Robust.Client.Graphics
return instance;
}
public void ClearFontCache()
{
foreach (var fontInstance in _loadedInstances)
{
fontInstance.Value.ClearSizeData();
}
}
private ScaledFontData _generateScaledDatum(FontInstanceHandle instance, float scale)
{
var ftFace = instance.FaceHandle.Face;
@@ -255,18 +246,6 @@ namespace Robust.Client.Graphics
FaceHandle = faceHandle;
}
public void ClearSizeData()
{
foreach (var scaleData in _scaledData)
{
foreach (var ownedTexture in scaleData.Value.AtlasTextures)
{
ownedTexture.Dispose();
}
}
_scaledData.Clear();
}
public Texture? GetCharTexture(Rune codePoint, float scale)
{
var glyph = GetGlyph(codePoint);

View File

@@ -61,9 +61,5 @@ namespace Robust.Client.Graphics
/// <returns>Null if not running on X11.</returns>
uint? GetX11WindowId();
void RegisterGridEcsEvents();
void RunOnWindowThread(Action action);
}
}

View File

@@ -5,8 +5,9 @@ namespace Robust.Client.Graphics
{
public interface IFontManager
{
public void ClearFontCache();
}
internal interface IFontManagerInternal : IFontManager
{
IFontFaceHandle Load(Stream stream);

View File

@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
@@ -22,20 +22,11 @@ namespace Robust.Client.Map
public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent;
private readonly Dictionary<ushort, Box2[]> _tileRegions = new();
private readonly Dictionary<ushort, Box2> _tileRegions = new();
public Box2 ErrorTileRegion { get; private set; }
/// <inheritdoc />
public Box2[]? TileAtlasRegion(Tile tile)
public Box2? TileAtlasRegion(Tile tile)
{
return TileAtlasRegion(tile.TypeId);
}
/// <inheritdoc />
public Box2[]? TileAtlasRegion(ushort tileType)
{
if (_tileRegions.TryGetValue(tileType, out var region))
if (_tileRegions.TryGetValue(tile.TypeId, out var region))
{
return region;
}
@@ -60,79 +51,39 @@ namespace Robust.Client.Map
const int tileSize = EyeManager.PixelsPerMeter;
var tileCount = defList.Select(x => (int)x.Variants).Sum() + 1;
var dimensionX = (int) Math.Ceiling(Math.Sqrt(defList.Count));
var dimensionY = (int) Math.Ceiling((float) defList.Count / dimensionX);
var dimensionX = (int) Math.Ceiling(Math.Sqrt(tileCount));
var dimensionY = (int) Math.Ceiling((float) tileCount / dimensionX);
var sheet = new Image<Rgba32>(dimensionX * tileSize, dimensionY * tileSize);
var imgWidth = dimensionX * tileSize;
var imgHeight = dimensionY * tileSize;
var sheet = new Image<Rgba32>(imgWidth, imgHeight);
// Add in the missing tile texture sprite as tile texture 0.
for (var i = 0; i < defList.Count; i++)
{
var w = (float) sheet.Width;
var h = (float) sheet.Height;
ErrorTileRegion = Box2.FromDimensions(
0, (h - EyeManager.PixelsPerMeter) / h,
tileSize / w, tileSize / h);
Image<Rgba32> image;
using (var stream = _resourceCache.ContentFileRead("/Textures/noTile.png"))
{
image = Image.Load<Rgba32>(stream);
}
var def = defList[i];
var column = i % dimensionX;
var row = i / dimensionX;
image.Blit(new UIBox2i(0, 0, tileSize, tileSize), sheet, Vector2i.Zero);
}
if (imgWidth >= 2048 || imgHeight >= 2048)
{
// Sanity warning, some machines don't have textures larger than this and need multiple atlases.
Logger.WarningS("clyde",
$"Tile texture atlas is ({imgWidth} x {imgHeight}), larger than 2048 x 2048. If you really need {tileCount} tiles, file an issue on RobustToolbox.");
}
var column = 1;
var row = 0;
foreach (var def in defList)
{
Image<Rgba32> image;
using (var stream = _resourceCache.ContentFileRead(new ResourcePath(def.Path) / $"{def.SpriteName}.png"))
{
image = Image.Load<Rgba32>(stream);
}
if (image.Width != (tileSize * def.Variants) || image.Height != tileSize)
if (image.Width != tileSize || image.Height != tileSize)
{
throw new NotSupportedException(
$"Unable to load {new ResourcePath(def.Path) / $"{def.SpriteName}.png"}, due to being unable to use tile texture with a dimension other than {tileSize}x({tileSize} * Variants).");
throw new NotSupportedException("Unable to use tiles with a dimension other than 32x32.");
}
var regionList = new Box2[def.Variants];
var point = new Vector2i(column * tileSize, row * tileSize);
for (var j = 0; j < def.Variants; j++)
{
var point = new Vector2i(column * tileSize, row * tileSize);
image.Blit(new UIBox2i(0, 0, image.Width, image.Height), sheet, point);
var box = new UIBox2i(0, 0, tileSize, tileSize).Translated(new Vector2i(j * tileSize, 0));
image.Blit(box, sheet, point);
var w = (float) sheet.Width;
var h = (float) sheet.Height;
var w = (float) sheet.Width;
var h = (float) sheet.Height;
regionList[j] = Box2.FromDimensions(
_tileRegions.Add(def.TileId,
Box2.FromDimensions(
point.X / w, (h - point.Y - EyeManager.PixelsPerMeter) / h,
tileSize / w, tileSize / h);
column++;
if (column >= dimensionX)
{
column = 0;
row++;
}
}
_tileRegions.Add(def.TileId, regionList);
tileSize / w, tileSize / h));
}
_tileTextureAtlas = Texture.LoadFromImage(sheet, "Tile Atlas");

View File

@@ -1,4 +1,3 @@
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -10,8 +9,6 @@ namespace Robust.Client.Map
/// </summary>
internal interface IClydeTileDefinitionManager : ITileDefinitionManager
{
Box2 ErrorTileRegion { get; }
/// <summary>
/// The texture atlas containing all the tiles.
/// </summary>
@@ -21,12 +18,6 @@ namespace Robust.Client.Map
/// Gets the region inside the texture atlas to use to draw a tile.
/// </summary>
/// <returns>If null, do not draw the tile at all.</returns>
Box2[]? TileAtlasRegion(Tile tile);
/// <summary>
/// Gets the region inside the texture atlas to use to draw a tile type.
/// </summary>
/// <returns>If null, do not draw the tile at all.</returns>
Box2[]? TileAtlasRegion(ushort tileType);
Box2? TileAtlasRegion(Tile tile);
}
}

View File

@@ -37,7 +37,7 @@ namespace Robust.Client.Physics
SimulateWorld((float) diff.TotalSeconds, true);
}
protected override void HandleMapCreated(MapChangedEvent eventArgs)
protected override void HandleMapCreated(object? sender, MapEventArgs eventArgs)
{
if (eventArgs.Map == MapId.Nullspace) return;
var mapUid = MapManager.GetMapEntityId(eventArgs.Map);

View File

@@ -31,7 +31,7 @@ namespace Robust.Client.Placement.Modes
var mapId = MouseCoords.GetMapId(pManager.EntityManager);
var snapToEntities = EntitySystem.Get<EntityLookupSystem>().GetEntitiesInRange(MouseCoords, SnapToRange)
var snapToEntities = IoCManager.Resolve<IEntityLookup>().GetEntitiesInRange(MouseCoords, SnapToRange)
.Where(entity => pManager.EntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype == pManager.CurrentPrototype && pManager.EntityManager.GetComponent<TransformComponent>(entity).MapID == mapId)
.OrderBy(entity => (pManager.EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - MouseCoords.ToMapPos(pManager.EntityManager)).LengthSquared)
.ToList();

View File

@@ -53,7 +53,7 @@ namespace Robust.Client.Placement.Modes
var topRight = new Vector2(CurrentTile.X + 0.99f, CurrentTile.Y + 0.99f);
var box = new Box2(bottomLeft, topRight);
return !EntitySystem.Get<EntityLookupSystem>().AnyEntitiesIntersecting(map, box);
return !IoCManager.Resolve<IEntityLookup>().AnyEntitiesIntersecting(map, box);
}
}
}

View File

@@ -22,7 +22,7 @@ using Robust.Shared.Utility;
namespace Robust.Client.Placement
{
public sealed partial class PlacementManager : IPlacementManager, IDisposable, IEntityEventSubscriber
public sealed partial class PlacementManager : IPlacementManager, IDisposable
{
[Dependency] private readonly IClientNetManager NetworkManager = default!;
[Dependency] public readonly IPlayerManager PlayerManager = default!;
@@ -185,7 +185,7 @@ namespace Robust.Client.Placement
_modeDictionary.Add(type.Name, type);
}
EntityManager.EventBus.SubscribeEvent<TileChangedEvent>(EventSource.Local, this, HandleTileChanged);
MapManager.TileChanged += HandleTileChanged;
_drawOverlay = new PlacementOverlay(this);
_overlayManager.AddOverlay(_drawOverlay);
@@ -328,7 +328,7 @@ namespace Robust.Client.Placement
}
}
private void HandleTileChanged(TileChangedEvent args)
private void HandleTileChanged(object? sender, TileChangedEventArgs args)
{
var coords = MapManager.GetGrid(args.NewTile.GridIndex).GridTileToLocal(args.NewTile.GridIndices);
_pendingTileChanges.RemoveAll(c => c.Item1 == coords);

View File

@@ -230,7 +230,7 @@ namespace Robust.Client.ResourceManagement
using (var manifestFile = cache.ContentFileRead(manifestPath))
{
if (manifestFile.CanSeek && manifestFile.Length <= 4096)
if (manifestFile.Length <= 4096)
{
// Most RSIs are actually tiny so if that's the case just load them into a stackalloc buffer.
// Avoids a ton of allocations with stream reader etc

View File

@@ -13,8 +13,8 @@
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
<ItemGroup>
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.2" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="5.0.3" />
<PackageReference Include="nfluidsynth" Version="0.3.1" />
<PackageReference Include="NVorbis" Version="0.10.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
@@ -25,9 +25,9 @@
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" />
</ItemGroup>
<ItemGroup Condition="'$(EnableClientScripting)' == 'True'">
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="3.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
<ProjectReference Include="..\Robust.Shared.Scripting\Robust.Shared.Scripting.csproj" />
</ItemGroup>
@@ -53,7 +53,6 @@
<Compile Update="UserInterface\CustomControls\DefaultWindow.xaml.cs">
<DependentUpon>DefaultWindow.xaml</DependentUpon>
</Compile>
<Compile Remove="Debugging\IDebugDrawing.cs" />
</ItemGroup>
<Import Project="..\MSBuild\Robust.Engine.targets" />
<PropertyGroup>

View File

@@ -1,6 +1,4 @@
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface.Controls
{
@@ -11,30 +9,6 @@ namespace Robust.Client.UserInterface.Controls
Window = window;
}
/// <summary>
/// Enable the UI autoscale system, this will scale down the UI for lower resolutions
/// </summary>
[ViewVariables]
public bool AutoScale { get; set; } = false;
/// <summary>
/// Minimum resolution to start clamping autoscale to 1
/// </summary>
[ViewVariables]
public Vector2i AutoScaleUpperCutoff { get; set; } = new Vector2i(1080, 720);
/// <summary>
/// Maximum resolution to start clamping autos scale to autoscale minimum
/// </summary>
[ViewVariables]
public Vector2i AutoScaleLowerCutoff { get; set; } = new Vector2i(520, 520);
/// <summary>
/// The minimum ui scale value that autoscale will scale to
/// </summary>
[ViewVariables]
public float AutoScaleMinimum { get; set; } = 0.5f;
public override float UIScale => UIScaleSet;
internal float UIScaleSet { get; set; }
public override IClydeWindow Window { get; }

View File

@@ -8,7 +8,6 @@ using System.Text;
using System.Threading.Tasks;
using Robust.Client.Graphics;
using Robust.Shared;
using Robust.Shared.Console;
using Robust.Shared.Asynchronous;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -165,11 +164,12 @@ namespace Robust.Client.UserInterface
{
if (OperatingSystem.IsMacOS())
{
// macOS seems pretty annoying about having the file dialog opened from the main windowing thread.
// So we are forced to execute this synchronously on the main windowing thread.
// nativefiledialog doesn't provide any form of async API, so this WILL lock up half the client.
// macOS seems pretty annoying about having the file dialog opened from the main thread.
// So we are forced to execute this synchronously on the main thread.
// Also I'm calling RunOnMainThread here to provide safety in case this is ran from a different thread.
// nativefiledialog doesn't provide any form of async API, so this WILL lock up the client.
var tcs = new TaskCompletionSource<string?>();
_clyde.RunOnWindowThread(() => tcs.SetResult(action()));
_taskManager.RunOnMainThread(() => tcs.SetResult(action()));
return tcs.Task;
}
@@ -388,17 +388,4 @@ namespace Robust.Client.UserInterface
SW_NFD_CANCEL,
}
}
public sealed class OpenFileCommand : IConsoleCommand
{
public string Command => "testopenfile";
public string Description => "";
public string Help => "";
public async void Execute(IConsoleShell shell, string argStr, string[] args)
{
var stream = await IoCManager.Resolve<IFileDialogManager>().OpenFile();
stream?.Dispose();
}
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Robust.Client.Graphics;
using Robust.Client.Input;
@@ -13,7 +12,6 @@ using Robust.Shared.Configuration;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
@@ -25,7 +23,6 @@ namespace Robust.Client.UserInterface
internal sealed class UserInterfaceManager : IUserInterfaceManagerInternal
{
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IFontManager _fontManager = default!;
[Dependency] private readonly IClydeInternal _clyde = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
@@ -954,37 +951,14 @@ namespace Robust.Client.UserInterface
private void WindowContentScaleChanged(WindowContentScaleEventArgs args)
{
if (_windowsToRoot.TryGetValue(args.Window.Id, out var root))
{
UpdateUIScale(root);
_fontManager.ClearFontCache();
}
}
private float CalculateAutoScale(WindowRoot root)
{
//Grab the OS UIScale or the value set through CVAR debug
var osScale = _configurationManager.GetCVar(CVars.DisplayUIScale);
osScale = osScale == 0f ? root.Window.ContentScale.X : osScale;
var windowSize = root.Window.RenderTarget.Size;
//Only run autoscale if it is enabled, otherwise default to just use OS UIScale
if (!root.AutoScale && (windowSize.X <= 0 || windowSize.Y <= 0)) return osScale;
var maxScaleRes = root.AutoScaleUpperCutoff;
var minScaleRes = root.AutoScaleLowerCutoff;
var autoScaleMin = root.AutoScaleMinimum;
float scaleRatioX;
float scaleRatioY;
//Calculate the scale ratios and clamp it between the maximums and minimums
scaleRatioX = Math.Clamp(((float) windowSize.X - minScaleRes.X) / (maxScaleRes.X - minScaleRes.X) * osScale, autoScaleMin, osScale);
scaleRatioY = Math.Clamp(((float) windowSize.Y - minScaleRes.Y) / (maxScaleRes.Y - minScaleRes.Y) * osScale, autoScaleMin, osScale);
//Take the smallest UIScale value and use it for UI scaling
return Math.Min(scaleRatioX, scaleRatioY);
}
private void UpdateUIScale(WindowRoot root)
{
root.UIScaleSet = CalculateAutoScale(root);
var newVal = _configurationManager.GetCVar(CVars.DisplayUIScale);
root.UIScaleSet = newVal == 0f ? root.Window.ContentScale.X : newVal;
_propagateUIScaleChanged(root);
root.InvalidateMeasure();
}
@@ -1003,7 +977,7 @@ namespace Robust.Client.UserInterface
{
if (!_windowsToRoot.TryGetValue(windowResizedEventArgs.Window.Id, out var root))
return;
UpdateUIScale(root);
root.InvalidateMeasure();
}

View File

@@ -22,7 +22,6 @@ namespace Robust.Client.Utility
.Texture;
}
[Obsolete("Use SpriteSystem")]
public static RSI.State GetState(this SpriteSpecifier.Rsi rsiSpecifier, IResourceCache cache)
{
if (cache.TryGetResource<RSIResource>(
@@ -37,7 +36,6 @@ namespace Robust.Client.Utility
return SpriteComponent.GetFallbackState(cache);
}
[Obsolete("Use SpriteSystem")]
public static Texture Frame0(this SpriteSpecifier specifier)
{
return specifier.RsiStateLike().Default;
@@ -82,7 +80,6 @@ namespace Robust.Client.Utility
return specifier.RsiStateLike();
}
[Obsolete("Use SpriteSystem")]
public static IRsiStateLike RsiStateLike(this SpriteSpecifier specifier)
{
var resC = IoCManager.Resolve<IResourceCache>();

View File

@@ -1 +0,0 @@
# Add your introductions here!

View File

@@ -1,2 +0,0 @@
- name: Example-article
href: example-article.md

View File

@@ -1,214 +0,0 @@
{
"metadata": [
{
"src":
[
{
"files": [
"**.csproj"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"**.xaml"
],
"src": "../Robust.UnitTesting"
}
],
"disableGitFeatures": false,
"disableDefaultFilter": false,
"dest": "api/Robust.UnitTesting"
},
{
"src":
[
{
"files": [
"**.csproj"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"**.xaml"
],
"src": "../Robust.Shared"
}
],
"disableGitFeatures": false,
"disableDefaultFilter": false,
"dest": "api/Robust.Shared"
},
{
"src":
[
{
"files": [
"**.csproj"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"**.xaml"
],
"src": "../Robust.Shared.Maths"
}
],
"disableGitFeatures": false,
"disableDefaultFilter": false,
"dest": "api/Robust.Shared.Maths"
},
{
"src":
[
{
"files": [
"**.csproj"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"**.xaml"
],
"src": "../Robust.Shared.Scripting"
}
],
"disableGitFeatures": false,
"disableDefaultFilter": false,
"dest": "api/Robust.Shared.Scripting"
},
{
"src":
[
{
"files": [
"**.csproj"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"**.xaml"
],
"src": "../Robust.Client"
}
],
"disableGitFeatures": false,
"disableDefaultFilter": false,
"dest": "api/Robust.Client"
},
{
"src":
[
{
"files": [
"**.csproj"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"**.xaml"
],
"src": "../Robust.Client.WebView"
}
],
"disableGitFeatures": false,
"disableDefaultFilter": false,
"dest": "api/Robust.Client.WebView"
},
{
"src":
[
{
"files": [
"**.csproj"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"**.xaml"
],
"src": "../Robust.Server"
}
],
"disableGitFeatures": false,
"disableDefaultFilter": false,
"dest": "api/Robust.Server"
},
{
"src":
[
{
"files": [
"**.csproj"
],
"exclude": [
"**/bin/**",
"**/obj/**",
"_site/**",
"**.xaml"
],
"src": "../Robust.LoaderApi"
}
],
"disableGitFeatures": false,
"disableDefaultFilter": false,
"dest": "api/Robust.LoaderApi/Robust.LoaderApi"
}
],
"build": {
"content": [
{
"files": [
"api/**/**.yml"
]
},
{
"files": [
"articles/**.md",
"articles/**/toc.yml",
"toc.yml",
"*.md"
]
}
],
"resource": [
{
"files": [
"images/**",
"favicon.ico",
"icon.svg"
]
}
],
"overwrite": [
{
"files": [
"apidoc/**.md"
],
"exclude": [
"obj/**",
"_site/**"
]
}
],
"dest": "_robust-site",
"globalMetadataFiles": [],
"fileMetadataFiles": [],
"template": [
"default",
"templates/darkfx"
],
"postProcessors": [],
"markdownEngineName": "markdig",
"noLangKeyword": false,
"keepFileLink": false,
"cleanupCacheHistory": false,
"disableGitFeatures": false
}
}

View File

@@ -1,6 +0,0 @@
# RobustToolbox DocFX
![](https://i.imgur.com/Sv3Swxg.png)
## Welcome to the RobustToolbox DocFX instance.
### Click one of the tabs above to see documentation for that particular project/namespace

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 Steffen Wilke
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,40 +0,0 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<div class="hidden-sm col-md-2" role="complementary">
<div class="sideaffix">
{{^_disableContribution}}
<div class="contribution">
<ul class="nav">
{{#docurl}}
<li>
<a href="{{docurl}}" class="contribution-link">{{__global.improveThisDoc}}</a>
</li>
{{/docurl}}
{{#sourceurl}}
<li>
<a href="{{sourceurl}}" class="contribution-link">{{__global.viewSource}}</a>
</li>
{{/sourceurl}}
</ul>
</div>
{{/_disableContribution}}
<div class="toggle-mode">
<div class="icon">
<i aria-hidden="true">☀</i>
</div>
<label class="switch">
<input type="checkbox" id="switch-style">
<span class="slider round"></span>
</label>
<div class="icon">
<i aria-hidden="true">☾</i>
</div>
</div>
<nav class="bs-docs-sidebar hidden-print hidden-xs hidden-sm affix" id="affix">
<h5>{{__global.inThisArticle}}</h5>
<div></div>
<!-- <p><a class="back-to-top" href="#top">Back to top</a><p> -->
</nav>
</div>
</div>

View File

@@ -1,29 +0,0 @@
{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<footer>
<div class="grad-bottom"></div>
<div class="footer">
<div class="container">
<span class="pull-right">
<a href="#top">Back to top</a>
</span>
<div class="pull-left">
{{{_appFooter}}}
{{^_appFooter}}<span>Generated by <strong>DocFX</strong></span>{{/_appFooter}}
</div>
<div class="toggle-mode pull-right visible-sm visible-xs">
<div class="icon">
<i aria-hidden="true">☀</i>
</div>
<label class="switch">
<input type="checkbox" id="switch-style-m">
<span class="slider round"></span>
</label>
<div class="icon">
<i aria-hidden="true">☾</i>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="{{_rel}}styles/toggle-theme.js"></script>
</footer>

View File

@@ -1,20 +0,0 @@
{{!Copyright (c) Oscar Vasquez. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}</title>
<meta name="viewport" content="width=device-width">
<meta name="title" content="{{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}">
<meta name="generator" content="docfx {{_docfxVersion}}">
{{#_description}}<meta name="description" content="{{_description}}">{{/_description}}
<link rel="shortcut icon" href="{{_rel}}{{{_appFaviconPath}}}{{^_appFaviconPath}}favicon.ico{{/_appFaviconPath}}">
<link rel="stylesheet" href="{{_rel}}styles/docfx.vendor.css">
<link rel="stylesheet" href="{{_rel}}styles/docfx.css">
<link rel="stylesheet" href="{{_rel}}styles/main.css">
<meta property="docfx:navrel" content="{{_navRel}}">
<meta property="docfx:tocrel" content="{{_tocRel}}">
{{#_noindex}}<meta name="searchOption" content="noindex">{{/_noindex}}
{{#_enableSearch}}<meta property="docfx:rel" content="{{_rel}}">{{/_enableSearch}}
{{#_enableNewTab}}<meta property="docfx:newtab" content="true">{{/_enableNewTab}}
</head>

View File

@@ -1,470 +0,0 @@
:root, body.dark-theme {
--color-foreground: #ccd5dc;
--color-navbar: #66666d;
--color-breadcrumb: #999;
--color-underline: #ddd;
--color-toc-hover: #fff;
--color-background: #2d2d30;
--color-background-subnav: #333337;
--color-background-dark: #1e1e1e;
--color-background-table-alt: #212123;
--color-background-quote: #69696e;
}
body.light-theme {
--color-foreground: #171717;
--color-breadcrumb: #4a4a4a;
--color-toc-hover: #4c4c4c;
--color-background: #ffffff;
--color-background-subnav: #f5f5f5;
--color-background-dark: #ddd;
--color-background-table-alt: #f9f9f9;
}
body {
color: var(--color-foreground);
line-height: 1.5;
font-size: 14px;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
word-wrap: break-word;
background-color: var(--color-background);
}
.btn.focus, .btn:focus, .btn:hover {
color: var(--color-foreground);
}
h1 {
font-weight: 600;
font-size: 32px;
}
h2 {
font-weight: 600;
font-size: 24px;
line-height: 1.8;
}
h3 {
font-weight: 600;
font-size: 20px;
line-height: 1.8;
}
h5 {
font-size: 14px;
padding: 10px 0px;
}
article h1, article h2, article h3, article h4 {
margin-top: 35px;
margin-bottom: 15px;
}
article h4 {
padding-bottom: 8px;
border-bottom: 2px solid var(--color-underline);
}
.navbar-brand>img {
color: var(--color-background);
}
.navbar {
border: none;
}
.subnav {
border-top: 1px solid var(--color-underline);
background-color: var(--color-background-subnav);
}
.sidenav, .fixed_header, .toc {
background-color: var(--color-background);
}
.navbar-inverse {
background-color: var(--color-background-dark);
z-index: 100;
}
.navbar-inverse .navbar-nav>li>a, .navbar-inverse .navbar-text {
color: var(--color-navbar);
background-color: var(--color-background-dark);
border-bottom: 3px solid transparent;
padding-bottom: 12px;
}
.navbar-inverse .navbar-nav>li>a:focus, .navbar-inverse .navbar-nav>li>a:hover {
color: var(--color-foreground);
background-color: var(--color-background-dark);
border-bottom: 3px solid var(--color-background-subnav);
transition: all ease 0.25s;
}
.navbar-inverse .navbar-nav>.active>a, .navbar-inverse .navbar-nav>.active>a:focus, .navbar-inverse .navbar-nav>.active>a:hover {
color: var(--color-foreground);
background-color: var(--color-background-dark);
border-bottom: 3px solid var(--color-foreground);
transition: all ease 0.25s;
}
.navbar-form .form-control {
border: none;
border-radius: 0;
}
.light-theme .navbar-brand svg {
filter: brightness(20%);
}
.toc .level1>li {
font-weight: 400;
}
.toc .nav>li>a {
color: var(--color-foreground);
}
.sidefilter {
background-color: var(--color-background);
border-left: none;
border-right: none;
}
.sidefilter {
background-color: var(--color-background);
border-left: none;
border-right: none;
}
.toc-filter {
padding: 10px;
margin: 0;
background-color: var(--color-background);
}
.toc-filter>input {
border: none;
border-radius: unset;
background-color: var(--color-background-subnav);
padding: 5px 0 5px 20px;
font-size: 90%
}
.toc-filter>.clear-icon {
position: absolute;
top: 17px;
right: 15px;
}
.toc-filter>input:focus {
color: var(--color-foreground);
transition: all ease 0.25s;
}
.toc-filter>.filter-icon {
display: none;
}
.sidetoc>.toc {
background-color: var(--color-background);
overflow-x: hidden;
}
.sidetoc {
background-color: var(--color-background);
border: none;
}
.alert {
background-color: inherit;
border: none;
padding: 10px 0;
border-radius: 0;
}
.alert>p {
margin-bottom: 0;
padding: 5px 10px;
border-bottom: 1px solid;
background-color: var(--color-background-dark);
}
.alert>h5 {
padding: 10px 15px;
margin-top: 0;
margin-bottom: 0;
text-transform: uppercase;
font-weight: bold;
border-top: 2px solid;
background-color: var(--color-background-dark);
border-radius: none;
}
.alert>ul {
margin-bottom: 0;
padding: 5px 40px;
}
.alert-info {
color: #1976d2;
}
.alert-warning {
color: #f57f17;
}
.alert-danger {
color: #d32f2f;
}
pre {
padding: 9.5px;
margin: 0 0 10px;
font-size: 13px;
word-break: break-all;
word-wrap: break-word;
background-color: var(--color-background-dark);
border-radius: 0;
border: none;
}
code {
background: var(--color-background-dark) !important;
border-radius: 2px;
}
.hljs {
color: var(--color-foreground);
}
.toc .nav>li.active>.expand-stub::before, .toc .nav>li.in>.expand-stub::before, .toc .nav>li.in.active>.expand-stub::before, .toc .nav>li.filtered>.expand-stub::before {
content: "▾";
}
.toc .nav>li>.expand-stub::before, .toc .nav>li.active>.expand-stub::before {
content: "▸";
}
.affix ul ul>li>a:before {
content: "|";
}
.breadcrumb {
background-color: var(--color-background-subnav);
}
.breadcrumb .label.label-primary {
background: #444;
border-radius: 0;
font-weight: normal;
font-size: 100%;
}
#breadcrumb .breadcrumb>li a {
border-radius: 0;
font-weight: normal;
font-size: 85%;
display: inline;
padding: 0 .6em 0;
line-height: 1;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
color: var(--color-breadcrumb);
}
#breadcrumb .breadcrumb>li a:hover {
color: var(--color-foreground);
transition: all ease 0.25s;
}
.breadcrumb>li+li:before {
content: "⯈";
font-size: 75%;
color: var(--color-background-dark);
padding: 0;
}
.light-theme .breadcrumb>li+li:before {
color: var(--color-foreground)
}
.toc .level1>li {
font-weight: 600;
font-size: 130%;
padding-left: 5px;
}
.footer {
border-top: none;
background-color: var(--color-background-dark);
padding: 15px 0;
font-size: 90%;
}
.toc .nav>li>a:hover, .toc .nav>li>a:focus {
color: var(--color-toc-hover);
transition: all ease 0.1s;
}
.form-control {
background-color: var(--color-background-subnav);
border: none;
border-radius: 0;
-webkit-box-shadow: none;
box-shadow: none;
}
.form-control:focus {
border-color: #66afe9;
outline: 0;
-webkit-box-shadow: none;
box-shadow: none;
}
input#search-query:focus {
color: var(--color-foreground);
}
.table-bordered, .table-bordered>tbody>tr>td, .table-bordered>tbody>tr>th, .table-bordered>tfoot>tr>td, .table-bordered>tfoot>tr>th, .table-bordered>thead>tr>td, .table-bordered>thead>tr>th {
border: 1px solid var(--color-background-dark);
}
.table-striped>tbody>tr:nth-of-type(odd) {
background-color: var(--color-background-table-alt);
}
blockquote {
padding: 10px 20px;
margin: 0 0 10px;
font-size: 110%;
border-left: 5px solid var(--color-background-quote);
color: var(--color-background-quote);
}
.pagination>.disabled>a, .pagination>.disabled>a:focus, .pagination>.disabled>a:hover, .pagination>.disabled>span, .pagination>.disabled>span:focus, .pagination>.disabled>span:hover {
background-color: var(--color-background-subnav);
border-color: var(--color-background-subnav);
}
.breadcrumb>li, .pagination {
display: inline;
}
.tabGroup a[role="tab"] {
border-bottom: 2px solid var(--color-background-dark);
}
.tabGroup a[role="tab"][aria-selected="true"] {
color: var(--color-foreground);
}
.tabGroup section[role="tabpanel"] {
border: 1px solid var(--color-background-dark);
}
.sideaffix > div.contribution > ul > li > a.contribution-link:hover {
background-color: var(--color-background);
}
.switch {
position: relative;
display: inline-block;
width: 40px;
height: 20px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 14px;
width: 14px;
left: 4px;
bottom: 3px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: #337ab7;
}
input:focus + .slider {
box-shadow: 0 0 1px #337ab7;
}
input:checked + .slider:before {
-webkit-transform: translateX(19px);
-ms-transform: translateX(19px);
transform: translateX(19px);
}
/* Rounded sliders */
.slider.round {
border-radius: 20px;
}
.slider.round:before {
border-radius: 50%;
}
.toggle-mode .icon {
display: inline-block;
}
.toggle-mode .icon i {
font-style: normal;
font-size: 17px;
display: inline-block;
padding-right: 7px;
padding-left: 7px;
vertical-align: middle;
}
@media (min-width: 1600px) {
.container {
width: 100%;
}
.sidefilter {
width: 18%;
}
.sidetoc {
width: 18%;
}
.article.grid-right {
margin-left: 19%;
}
.sideaffix {
width: 11.5%;
}
.affix ul>li.active>a {
white-space: initial;
}
.affix ul>li>a {
width: 99%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}

View File

@@ -1,35 +0,0 @@
const sw = document.getElementById("switch-style"), sw_mobile = document.getElementById("switch-style-m"), b = document.body;
if (b) {
function toggleTheme(target, dark) {
target.classList.toggle("dark-theme", dark)
target.classList.toggle("light-theme", !dark)
}
function switchEventListener() {
toggleTheme(b, this.checked);
if (window.localStorage) {
this.checked ? localStorage.setItem("theme", "dark-theme") : localStorage.setItem("theme", "light-theme")
}
}
var isDarkTheme = !window.localStorage || !window.localStorage.getItem("theme") || window.localStorage && localStorage.getItem("theme") === "dark-theme";
if(sw && sw_mobile){
sw.checked = isDarkTheme;
sw_mobile.checked = isDarkTheme;
sw.addEventListener("change", switchEventListener);
sw_mobile.addEventListener("change", switchEventListener);
// sync state between switches
sw.addEventListener("change", function() {
sw_mobile.checked = this.checked;
});
sw_mobile.addEventListener("change", function() {
sw.checked = this.checked;
});
}
toggleTheme(b, isDarkTheme);
}

View File

@@ -1,29 +0,0 @@
- name: Articles
href: articles/
# Client
- name: Robust.Client
href: api/Robust.Client/
homepage: api/Robust.Client/Robust.Client.html
- name: Robust.Client.WebView
href: api/Robust.Client.WebView/
#Loader Api
- name: Robust.LoaderApi
href: api/Robust.LoaderApi/Robust.LoaderApi/
# Server
- name: Robust.Server
href: api/Robust.Server/
# Shared
- name: Robust.Shared
href: api/Robust.Shared/
- name: Robust.Shared.Maths
href: api/Robust.Shared.Maths/
- name: Robust.Shared.Scripting
href: api/Robust.Shared.Scripting/
# Unit testing
- name: Robust.UnitTesting
href: api/Robust.UnitTesting/

View File

@@ -66,17 +66,18 @@ namespace Robust.Server
[Dependency] private readonly IConfigurationManagerInternal _config = default!;
[Dependency] private readonly IServerEntityManager _entityManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly ILogManager _log = default!;
[Dependency] private readonly IRobustSerializer _serializer = default!;
[Dependency] private readonly IGameTiming _time = default!;
[Dependency] private readonly IResourceManagerInternal _resources = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ITimerManager _timerManager = default!;
[Dependency] private readonly ITimerManager timerManager = default!;
[Dependency] private readonly IServerGameStateManager _stateManager = default!;
[Dependency] private readonly IServerNetManager _network = default!;
[Dependency] private readonly ISystemConsoleManager _systemConsole = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
[Dependency] private readonly IRuntimeLog runtimeLog = default!;
[Dependency] private readonly IModLoaderInternal _modLoader = default!;
[Dependency] private readonly IWatchdogApi _watchdogApi = default!;
[Dependency] private readonly HubManager _hubManager = default!;
@@ -126,7 +127,7 @@ namespace Robust.Server
else
Logger.InfoS("srv", $"{reason}, shutting down...");
_shutdownReason = reason ?? "Shutting down";
_shutdownReason = reason;
if (_mainLoop != null) _mainLoop.Running = false;
if (_logHandler != null)
@@ -343,6 +344,7 @@ namespace Robust.Server
_consoleHost.Initialize();
_entityManager.Startup();
_mapManager.Startup();
IoCManager.Resolve<IEntityLookup>().Startup();
_stateManager.Initialize();
var reg = _entityManager.ComponentFactory.GetRegistration<TransformComponent>();
@@ -601,6 +603,7 @@ namespace Robust.Server
_network.Shutdown($"Server shutting down: {_shutdownReason}");
// shutdown entities
IoCManager.Resolve<IEntityLookup>().Shutdown();
_entityManager.Cleanup();
if (_config.GetCVar(CVars.LogRuntimeLog))
@@ -611,7 +614,7 @@ namespace Robust.Server
Directory.CreateDirectory(relPath);
var pathToWrite = Path.Combine(relPath,
"Runtime-" + DateTime.Now.ToString("yyyy-MM-dd-THH-mm-ss") + ".txt");
File.WriteAllText(pathToWrite, _runtimeLog.Display(), EncodingHelpers.UTF8);
File.WriteAllText(pathToWrite, runtimeLog.Display(), EncodingHelpers.UTF8);
}
AppDomain.CurrentDomain.ProcessExit -= ProcessExiting;
@@ -656,8 +659,7 @@ namespace Robust.Server
using (TickUsage.WithLabels("Timers").NewTimer())
{
_consoleHost.CommandBufferExecute();
_timerManager.UpdateTimers(frameEventArgs);
timerManager.UpdateTimers(frameEventArgs);
}
using (TickUsage.WithLabels("AsyncTasks").NewTimer())
@@ -668,6 +670,8 @@ namespace Robust.Server
// Pass Histogram into the IEntityManager.Update so it can do more granular measuring.
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, noPredictions: false, TickUsage);
_lookup.Update();
using (TickUsage.WithLabels("PostEngine").NewTimer())
{
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);

View File

@@ -311,7 +311,7 @@ namespace Robust.Server.Bql
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
var radius = (float)(double)arguments[0];
var entityLookup = EntitySystem.Get<EntityLookupSystem>();
var entityLookup = IoCManager.Resolve<IEntityLookup>();
// TODO: Make this a foreach and reduce LINQ chain because it'll allocate a LOT
//BUG: GetEntitiesInRange effectively uses manhattan distance. This is not intended, near is supposed to be circular.

View File

@@ -29,13 +29,14 @@ namespace Robust.Server.Console.Commands
var mapId = new MapId(int.Parse(args[0]));
var mapMgr = IoCManager.Resolve<IMapManager>();
var pauseMgr = IoCManager.Resolve<IPauseManager>();
if (!mapMgr.MapExists(mapId))
{
mapMgr.CreateMap(mapId);
if (args.Length >= 2 && args[1] == "false")
{
mapMgr.AddUninitializedMap(mapId);
pauseMgr.AddUninitializedMap(mapId);
}
shell.WriteLine($"Map with ID {mapId} created.");
@@ -218,11 +219,7 @@ namespace Robust.Server.Console.Commands
}
IoCManager.Resolve<IMapLoader>().LoadMap(mapId, args[1]);
if (mapManager.MapExists(mapId))
shell.WriteLine($"Map {mapId} has been loaded from {args[1]}.");
else
shell.WriteError($"Error while loading map from {args[1]}.");
shell.WriteLine($"Map {mapId} has been loaded from {args[1]}.");
}
}
@@ -321,6 +318,7 @@ namespace Robust.Server.Console.Commands
}
var mapManager = IoCManager.Resolve<IMapManager>();
var pauseManager = IoCManager.Resolve<IPauseManager>();
var arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
@@ -331,13 +329,13 @@ namespace Robust.Server.Console.Commands
return;
}
if (mapManager.IsMapInitialized(mapId))
if (pauseManager.IsMapInitialized(mapId))
{
shell.WriteError("Map is already initialized!");
return;
}
mapManager.DoMapInitialize(mapId);
pauseManager.DoMapInitialize(mapId);
}
}
@@ -350,16 +348,17 @@ namespace Robust.Server.Console.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mapManager = IoCManager.Resolve<IMapManager>();
var pauseManager = IoCManager.Resolve<IPauseManager>();
var msg = new StringBuilder();
foreach (var mapId in mapManager.GetAllMapIds().OrderBy(id => id.Value))
{
msg.AppendFormat("{0}: init: {1}, paused: {2}, ent: {3}, grids: {4}\n",
mapId, mapManager.IsMapInitialized(mapId),
mapManager.IsMapPaused(mapId),
mapManager.GetMapEntityId(mapId),
string.Join(",", mapManager.GetAllMapGrids(mapId).Select(grid => grid.Index)));
mapId, pauseManager.IsMapInitialized(mapId),
pauseManager.IsMapPaused(mapId),
string.Join(",", mapManager.GetAllMapGrids(mapId).Select(grid => grid.Index)),
mapManager.GetMapEntityId(mapId));
}
shell.WriteLine(msg.ToString());
@@ -381,7 +380,7 @@ namespace Robust.Server.Console.Commands
foreach (var grid in mapManager.GetAllGrids().OrderBy(grid => grid.Index.Value))
{
msg.AppendFormat("{0}: map: {1}, ent: {2}, pos: {3} \n",
grid.Index, grid.ParentMapId, grid.GridEntityId, grid.WorldPosition);
grid.Index, grid.ParentMapId, grid.WorldPosition, grid.GridEntityId);
}
shell.WriteLine(msg.ToString());

View File

@@ -109,8 +109,9 @@ namespace Robust.Server.Console.Commands
private void SetupPlayer(MapId mapId, IConsoleShell shell, IPlayerSession? player, IMapManager mapManager)
{
if (mapId == MapId.Nullspace) return;
mapManager.SetMapPaused(mapId, false);
var mapUid = mapManager.GetMapEntityIdOrThrow(mapId);
var pauseManager = IoCManager.Resolve<IPauseManager>();
pauseManager.SetMapPaused(mapId, false);
var mapUid = IoCManager.Resolve<IMapManager>().GetMapEntityIdOrThrow(mapId);
IoCManager.Resolve<IEntityManager>().GetComponent<SharedPhysicsMapComponent>(mapUid).Gravity = new Vector2(0, -9.8f);
return;

View File

@@ -3,7 +3,7 @@ using Robust.Shared.GameStates;
namespace Robust.Server.GameObjects;
public sealed class AppearanceSystem : SharedAppearanceSystem
internal sealed class AppearanceSystem : SharedAppearanceSystem
{
public override void Initialize()
{

View File

@@ -1,4 +1,4 @@
using JetBrains.Annotations;
using JetBrains.Annotations;
using Robust.Server.Physics;
using Robust.Shared;
using Robust.Shared.Configuration;
@@ -37,7 +37,7 @@ namespace Robust.Server.GameObjects
collideComp.BodyType = BodyType.Static;
}
protected override void HandleMapCreated(MapChangedEvent eventArgs)
protected override void HandleMapCreated(object? sender, MapEventArgs eventArgs)
{
if (eventArgs.Map == MapId.Nullspace) return;
var mapUid = MapManager.GetMapEntityIdOrThrow(eventArgs.Map);

View File

@@ -5,31 +5,30 @@ namespace Robust.Server.GameStates;
public struct ChunkIndicesEnumerator
{
private Vector2i _bottomLeft;
private Vector2i _topRight;
private Vector2i _topLeft;
private Vector2i _bottomRight;
private int _x;
private int _y;
public ChunkIndicesEnumerator(Vector2 viewPos, float range, float chunkSize)
public ChunkIndicesEnumerator(Box2 viewBox, float chunkSize)
{
_bottomLeft = ((viewPos - range) / chunkSize).Floored();
// Also floor this as we get the whole chunk anyway.
_topRight = ((viewPos + range) / chunkSize).Floored();
_topLeft = (viewBox.TopLeft / chunkSize).Floored();
_bottomRight = (viewBox.BottomRight / chunkSize).Floored();
_x = _bottomLeft.X;
_y = _bottomLeft.Y;
_x = _topLeft.X;
_y = _bottomRight.Y;
}
public bool MoveNext([NotNullWhen(true)] out Vector2i? chunkIndices)
{
if (_y > _topRight.Y)
if (_y > _topLeft.Y)
{
_x++;
_y = _bottomLeft.Y;
_y = _bottomRight.Y;
}
if (_x > _topRight.X)
if (_x > _bottomRight.X)
{
chunkIndices = null;
return false;

View File

@@ -37,6 +37,7 @@ public interface IPVSCollection
public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : IComparable<TIndex>, IEquatable<TIndex>
{
[Shared.IoC.Dependency] private readonly IEntityManager _entityManager = default!;
[Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector2i GetChunkIndices(Vector2 coordinates)
@@ -80,14 +81,14 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
private readonly List<(GameTick tick, TIndex index)> _deletionHistory = new();
/// <summary>
/// An index containing the <see cref="IIndexLocation"/>s of all <see cref="TIndex"/>.
/// An index containing the <see cref="IndexLocation"/>s of all <see cref="TIndex"/>.
/// </summary>
private readonly Dictionary<TIndex, IIndexLocation> _indexLocations = new();
private readonly Dictionary<TIndex, IndexLocation> _indexLocations = new();
/// <summary>
/// Buffer of all locationchanges since the last process call
/// </summary>
private readonly Dictionary<TIndex, IIndexLocation> _locationChangeBuffer = new();
private readonly Dictionary<TIndex, IndexLocation> _locationChangeBuffer = new();
/// <summary>
/// Buffer of all indexremovals since the last process call
/// </summary>
@@ -102,7 +103,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
{
var changedIndices = new HashSet<TIndex>(_locationChangeBuffer.Keys);
var changedChunkLocations = new HashSet<IIndexLocation>();
var changedChunkLocations = new HashSet<IndexLocation>();
foreach (var (index, tick) in _removalBuffer)
{
//changes dont need to be computed if we are removing the index anyways
@@ -153,7 +154,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
public HashSet<TIndex>.Enumerator GetElementsForSession(ICommonSession session) => _localOverrides[session].GetEnumerator();
private void AddIndexInternal(TIndex index, IIndexLocation location)
private void AddIndexInternal(TIndex index, IndexLocation location)
{
switch (location)
{
@@ -185,7 +186,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
_indexLocations.Add(index, location);
}
private IIndexLocation? RemoveIndexInternal(TIndex index)
private IndexLocation? RemoveIndexInternal(TIndex index)
{
// the index might be gone due to disconnects/grid-/map-deletions
if (!_indexLocations.TryGetValue(index, out var location))
@@ -351,14 +352,14 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
var gridId = coordinates.GetGridId(_entityManager);
if (gridId != GridId.Invalid)
{
var gridIndices = GetChunkIndices(coordinates.Position);
var gridIndices = GetChunkIndices(_mapManager.GetGrid(gridId).LocalToGrid(coordinates));
UpdateIndex(index, gridId, gridIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
return;
}
var mapCoordinates = coordinates.ToMap(_entityManager);
var mapIndices = GetChunkIndices(coordinates.Position);
UpdateIndex(index, mapCoordinates.MapId, mapIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
var mapId = coordinates.GetMapId(_entityManager);
var mapIndices = GetChunkIndices(coordinates.ToMapPos(_entityManager));
UpdateIndex(index, mapId, mapIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
}
/// <summary>
@@ -391,7 +392,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
RegisterUpdate(index, new MapChunkLocation(mapId, chunkIndices));
}
private void RegisterUpdate(TIndex index, IIndexLocation location)
private void RegisterUpdate(TIndex index, IndexLocation location)
{
if(_indexLocations.TryGetValue(index, out var oldLocation) && oldLocation == location) return;
@@ -399,78 +400,14 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
}
#endregion
#region IndexLocations
private abstract record IndexLocation;
private record MapChunkLocation(MapId MapId, Vector2i ChunkIndices) : IndexLocation;
private record GridChunkLocation(GridId GridId, Vector2i ChunkIndices) : IndexLocation;
private record GlobalOverride : IndexLocation;
private record LocalOverride(ICommonSession Session) : IndexLocation;
#endregion
}
#region IndexLocations
public interface IIndexLocation {};
public interface IChunkIndexLocation{ };
public struct MapChunkLocation : IIndexLocation, IChunkIndexLocation, IEquatable<MapChunkLocation>
{
public MapChunkLocation(MapId mapId, Vector2i chunkIndices)
{
MapId = mapId;
ChunkIndices = chunkIndices;
}
public MapId MapId { get; init; }
public Vector2i ChunkIndices { get; init; }
public bool Equals(MapChunkLocation other)
{
return MapId.Equals(other.MapId) && ChunkIndices.Equals(other.ChunkIndices);
}
public override bool Equals(object? obj)
{
return obj is MapChunkLocation other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(MapId, ChunkIndices);
}
}
public struct GridChunkLocation : IIndexLocation, IChunkIndexLocation, IEquatable<GridChunkLocation>
{
public GridChunkLocation(GridId gridId, Vector2i chunkIndices)
{
GridId = gridId;
ChunkIndices = chunkIndices;
}
public GridId GridId { get; init; }
public Vector2i ChunkIndices { get; init; }
public bool Equals(GridChunkLocation other)
{
return GridId.Equals(other.GridId) && ChunkIndices.Equals(other.ChunkIndices);
}
public override bool Equals(object? obj)
{
return obj is GridChunkLocation other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(GridId, ChunkIndices);
}
}
public struct GlobalOverride : IIndexLocation { }
public struct LocalOverride : IIndexLocation
{
public LocalOverride(ICommonSession session)
{
Session = session;
}
public ICommonSession Session { get; init; }
}
#endregion

File diff suppressed because it is too large Load Diff

View File

@@ -1,174 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.ObjectPool;
namespace Robust.Server.GameStates;
public sealed class RobustTree<T> where T : notnull
{
private Dictionary<T, TreeNode> _nodeIndex = new();
private Dictionary<T, T> _parents = new();
public readonly HashSet<T> RootNodes = new();
private ObjectPool<HashSet<T>> _pool;
public RobustTree(ObjectPool<HashSet<T>>? pool = null)
{
_pool = pool ?? new DefaultObjectPool<HashSet<T>>(new PVSSystem.SetPolicy<T>());
}
public void Clear()
{
foreach (var value in _nodeIndex.Values)
{
if(value.Children != null)
_pool.Return(value.Children);
}
_nodeIndex.Clear();
_parents.Clear();
RootNodes.Clear();
}
public TreeNode this[T index] => _nodeIndex[index];
public void Remove(T value, bool mend = false)
{
if (!_nodeIndex.TryGetValue(value, out var node))
throw new InvalidOperationException("Node doesnt exist.");
if (RootNodes.Contains(value))
{
if (node.Children != null)
{
foreach (var child in node.Children)
{
_parents.Remove(child);
RootNodes.Add(child);
}
_pool.Return(node.Children);
}
RootNodes.Remove(value);
_nodeIndex.Remove(value);
return;
}
if (_parents.TryGetValue(value, out var parent))
{
if (node.Children != null)
{
foreach (var child in node.Children)
{
if (mend)
{
_parents[child] = parent;
var children = _nodeIndex[parent].Children;
if (children == null)
{
children = _pool.Get();
_nodeIndex[parent] = _nodeIndex[parent].WithChildren(children);
}
children.Add(child);
}
else
{
_parents.Remove(child);
RootNodes.Add(child);
}
}
_pool.Return(node.Children);
}
_parents.Remove(value);
_nodeIndex.Remove(value);
}
throw new InvalidOperationException("Node neither had a parent nor was a RootNode.");
}
public TreeNode Set(T rootNode)
{
//root node, for now
if (_nodeIndex.TryGetValue(rootNode, out var node))
{
if(!RootNodes.Contains(rootNode))
throw new InvalidOperationException("Node already exists as non-root node.");
return node;
}
node = new TreeNode(rootNode);
_nodeIndex.Add(rootNode, node);
RootNodes.Add(rootNode);
return node;
}
public TreeNode Set(T child, T parent)
{
if (!_nodeIndex.TryGetValue(parent, out var parentNode))
parentNode = Set(parent);
if (parentNode.Children == null)
{
_nodeIndex[parent] = parentNode = parentNode.WithChildren(_pool.Get());
}
if (_nodeIndex.TryGetValue(child, out var existingNode))
{
if (RootNodes.Contains(child))
{
parentNode.Children!.Add(existingNode.Value);
RootNodes.Remove(child);
_parents.Add(child, parent);
return existingNode;
}
if (!_parents.TryGetValue(child, out var previousParent) || _nodeIndex.TryGetValue(previousParent, out var previousParentNode))
throw new InvalidOperationException("Could not find old parent for non-root node.");
previousParentNode.Children?.Remove(existingNode.Value);
parentNode.Children!.Add(existingNode.Value);
_parents[child] = parent;
return existingNode;
}
existingNode = new TreeNode(child);
_nodeIndex.Add(child, existingNode);
parentNode.Children!.Add(existingNode.Value);
_parents.Add(child, parent);
return existingNode;
}
public readonly struct TreeNode : IEquatable<TreeNode>
{
public readonly T Value;
public readonly HashSet<T>? Children;
public TreeNode(T value, HashSet<T>? children = null)
{
Value = value;
Children = children;
}
public bool Equals(TreeNode other)
{
return Value.Equals(other.Value) && Children?.Equals(other.Children) == true;
}
public override bool Equals(object? obj)
{
return obj is TreeNode other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(Value, Children);
}
public TreeNode WithChildren(HashSet<T> children)
{
return new TreeNode(Value, children);
}
}
}

View File

@@ -1,12 +1,9 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.ObjectPool;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Enums;
@@ -128,37 +125,6 @@ namespace Robust.Server.GameStates
// people not in the game don't get states
var players = _playerManager.ServerSessions.Where(o => o.Status == SessionStatus.InGame).ToArray();
//todo paul oh my god make this less shit
EntityQuery<MetaDataComponent> metadataQuery = default!;
EntityQuery<TransformComponent> transformQuery = default!;
HashSet<int>[] playerChunks = default!;
EntityUid[][] viewerEntities = default!;
(Dictionary<EntityUid, MetaDataComponent> metadata, RobustTree<EntityUid> tree)?[] chunkCache = default!;
if (_pvs.CullingEnabled)
{
List<(uint, IChunkIndexLocation)> chunks;
(chunks, playerChunks, viewerEntities) = _pvs.GetChunks(players);
const int ChunkBatchSize = 2;
var chunksCount = chunks.Count;
var chunkBatches = (int) MathF.Ceiling((float) chunksCount / ChunkBatchSize);
chunkCache =
new (Dictionary<EntityUid, MetaDataComponent> metadata, RobustTree<EntityUid> tree)?[chunksCount];
transformQuery = _entityManager.GetEntityQuery<TransformComponent>();
metadataQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
Parallel.For(0, chunkBatches, i =>
{
var start = i * ChunkBatchSize;
var end = Math.Min(start + ChunkBatchSize, chunksCount);
for (var j = start; j < end; ++j)
{
var (visMask, chunkIndexLocation) = chunks[j];
chunkCache[j] = _pvs.CalculateChunk(chunkIndexLocation, visMask, transformQuery, metadataQuery);
}
});
}
const int BatchSize = 2;
var batches = (int) MathF.Ceiling((float) players.Length / BatchSize);
@@ -169,9 +135,11 @@ namespace Robust.Server.GameStates
for (var j = start; j < end; ++j)
{
var session = players[j];
try
{
SendStateUpdate(j);
SendStateUpdate(session);
}
catch (Exception e) // Catch EVERY exception
{
@@ -180,10 +148,8 @@ namespace Robust.Server.GameStates
}
});
void SendStateUpdate(int sessionIndex)
void SendStateUpdate(IPlayerSession session)
{
var session = players[sessionIndex];
// KILL IT WITH FIRE
if(mainThread != Thread.CurrentThread)
IoCManager.InitThread(new DependencyCollection(parentDeps), true);
@@ -195,10 +161,7 @@ namespace Robust.Server.GameStates
DebugTools.Assert("Why does this channel not have an entry?");
}
var (entStates, deletions) = _pvs.CullingEnabled
? _pvs.CalculateEntityStates(session, lastAck, _gameTiming.CurTick, chunkCache,
playerChunks[sessionIndex], metadataQuery, transformQuery, viewerEntities[sessionIndex])
: _pvs.GetAllEntityStates(session, lastAck, _gameTiming.CurTick);
var (entStates, deletions) = _pvs.CalculateEntityStates(session, lastAck, _gameTiming.CurTick);
var playerStates = _playerManager.GetPlayerStates(lastAck);
var mapData = _mapManager.GetStateData(lastAck);
@@ -209,6 +172,8 @@ namespace Robust.Server.GameStates
InterlockedHelper.Min(ref oldestAckValue, lastAck.Value);
DebugTools.Assert(state.MapData?.CreatedMaps is null || (state.MapData?.CreatedMaps is not null && state.EntityStates.HasContents), "Sending new maps, but no entity state.");
// actually send the state
var stateUpdateMessage = _networkManager.CreateNetMessage<MsgState>();
stateUpdateMessage.State = state;
@@ -229,8 +194,6 @@ namespace Robust.Server.GameStates
_networkManager.ServerSendMessage(stateUpdateMessage, channel);
}
if(_pvs.CullingEnabled)
_pvs.ReturnToPool(chunkCache, playerChunks);
_pvs.Cleanup(_playerManager.ServerSessions);
var oldestAck = new GameTick(oldestAckValue);

View File

@@ -24,6 +24,7 @@ using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using YamlDotNet.Core;
using YamlDotNet.RepresentationModel;
@@ -43,6 +44,7 @@ namespace Robust.Server.Maps
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IServerEntityManagerInternal _serverEntityManager = default!;
[Dependency] private readonly IPauseManager _pauseManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public event Action<YamlStream, string>? LoadedMapData;
@@ -52,7 +54,7 @@ namespace Robust.Server.Maps
{
var grid = _mapManager.GetGrid(gridId);
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _prototypeManager);
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _prototypeManager);
context.RegisterGrid(grid);
var root = context.Serialize();
var document = new YamlDocument(root);
@@ -98,7 +100,7 @@ namespace Robust.Server.Maps
throw new InvalidDataException("Cannot instance map with multiple grids as blueprint.");
}
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager,
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager,
_prototypeManager, (YamlMappingNode) data.RootNode, mapId, options);
context.Deserialize();
grid = context.Grids[0];
@@ -118,7 +120,7 @@ namespace Robust.Server.Maps
_serverEntityManager.GetComponent<MetaDataComponent>(entity).EntityLifeStage = EntityLifeStage.MapInitialized;
}
}
else if (_mapManager.IsMapInitialized(mapId))
else if (_pauseManager.IsMapInitialized(mapId))
{
foreach (var entity in context.Entities)
{
@@ -126,7 +128,7 @@ namespace Robust.Server.Maps
}
}
if (_mapManager.IsMapPaused(mapId))
if (_pauseManager.IsMapPaused(mapId))
{
foreach (var entity in context.Entities)
{
@@ -139,7 +141,7 @@ namespace Robust.Server.Maps
public void SaveMap(MapId mapId, string yamlPath)
{
Logger.InfoS("map", $"Saving map {mapId} to {yamlPath}");
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _prototypeManager);
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager, _prototypeManager);
foreach (var grid in _mapManager.GetAllMapGrids(mapId))
{
context.RegisterGrid(grid);
@@ -205,7 +207,7 @@ namespace Robust.Server.Maps
LoadedMapData?.Invoke(data.Stream, resPath.ToString());
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager,
var context = new MapContext(_mapManager, _tileDefinitionManager, _serverEntityManager, _pauseManager,
_prototypeManager, (YamlMappingNode) data.RootNode, mapId, options);
context.Deserialize();
@@ -224,12 +226,12 @@ namespace Robust.Server.Maps
private readonly IMapManagerInternal _mapManager;
private readonly ITileDefinitionManager _tileDefinitionManager;
private readonly IServerEntityManagerInternal _serverEntityManager;
private readonly IPauseManager _pauseManager;
private readonly IPrototypeManager _prototypeManager;
private readonly MapLoadOptions? _loadOptions;
private readonly Dictionary<GridId, int> GridIDMap = new();
public readonly List<MapGrid> Grids = new();
private readonly List<GridId> _readGridIndices = new();
private readonly Dictionary<EntityUid, int> EntityUidMap = new();
private readonly Dictionary<int, EntityUid> UidEntityMap = new();
@@ -258,11 +260,12 @@ namespace Robust.Server.Maps
public bool MapIsPostInit { get; private set; }
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
IServerEntityManagerInternal entities, IPrototypeManager prototypeManager)
IServerEntityManagerInternal entities, IPauseManager pauseManager, IPrototypeManager prototypeManager)
{
_mapManager = maps;
_tileDefinitionManager = tileDefs;
_serverEntityManager = entities;
_pauseManager = pauseManager;
_prototypeManager = prototypeManager;
RootNode = new YamlMappingNode();
@@ -280,12 +283,13 @@ namespace Robust.Server.Maps
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
IServerEntityManagerInternal entities,
IPrototypeManager prototypeManager,
IPauseManager pauseManager, IPrototypeManager prototypeManager,
YamlMappingNode node, MapId targetMapId, MapLoadOptions options)
{
_mapManager = maps;
_tileDefinitionManager = tileDefs;
_serverEntityManager = entities;
_pauseManager = pauseManager;
_loadOptions = options;
RootNode = node;
@@ -315,8 +319,9 @@ namespace Robust.Server.Maps
// Create the new map.
AllocMap();
// Maps grid section indices to GridIds, for deserializing GridIds on entities.
ReadGridSectionIndices();
// Load grids.
ReadTileMapSection();
ReadGridSection();
// Entities are first allocated. This allows us to know the future UID of all entities on the map before
// even ExposeData is loaded. This allows us to resolve serialized EntityUid instances correctly.
@@ -325,11 +330,8 @@ namespace Robust.Server.Maps
// Actually instance components and run ExposeData on them.
FinishEntitiesLoad();
// Load grids.
ReadTileMapSection();
// Reads the grid section, allocates MapGrids, and maps them to their respective MapGridComponents.
ReadGridSection();
// Finish binding MapGrids to their respective MapGridComponents.
BindGridComponents();
// Clear the net tick numbers so that components from prototypes (not modified by map)
// aren't sent over the wire initially.
@@ -477,20 +479,18 @@ namespace Robust.Server.Maps
}
}
private void ReadGridSection()
private void BindGridComponents()
{
// There were no new grids, nothing to do here.
if(_readGridIndices.Count == 0)
if(Grids.Count == 0)
return;
// MapGrids already contain their assigned GridId from their ctor, and the MapComponents just got deserialized.
// Now we need to actually bind the MapGrids to their components so that you can resolve GridId -> EntityUid
// After doing this, it should be 100% safe to use the MapManager API like normal.
var yamlGrids = RootNode.GetNode<YamlSequenceNode>("grids");
// get ents that the grids will bind to
var gridComps = new Dictionary<GridId, MapGridComponent>(_readGridIndices.Count);
var gridComps = new Dictionary<GridId, MapGridComponent>(Grids.Count);
// linear search for new grid comps
foreach (var tuple in _entitiesToDeserialize)
@@ -507,54 +507,15 @@ namespace Robust.Server.Maps
gridComps[gridComp.GridIndex] = gridComp;
}
for (var index = 0; index < _readGridIndices.Count; index++)
// Actually bind them
foreach (var mapGrid in Grids)
{
// Here is where the implicit index pairing magic happens from the yaml.
var gridIndex = _readGridIndices[index];
var yamlGrid = (YamlMappingNode)yamlGrids.Children[index];
// designed to throw if something is broken, every grid must map to an ent
var gridComp = gridComps[gridIndex];
DebugTools.Assert(gridComp.GridIndex == gridIndex);
YamlMappingNode yamlGridInfo = (YamlMappingNode)yamlGrid["settings"];
YamlSequenceNode yamlGridChunks = (YamlSequenceNode)yamlGrid["chunks"];
var grid = AllocateMapGrid(gridComp, yamlGridInfo);
foreach (var chunkNode in yamlGridChunks.Cast<YamlMappingNode>())
{
YamlGridSerializer.DeserializeChunk(_mapManager, grid, chunkNode, _tileMap!, _tileDefinitionManager);
}
Grids.Add(grid); // Grids are kept in index order
var gridComp = gridComps[mapGrid.Index];
_mapManager.BindGrid(gridComp, mapGrid);
}
}
private static MapGrid AllocateMapGrid(MapGridComponent gridComp, YamlMappingNode yamlGridInfo)
{
// sane defaults
ushort csz = 16;
ushort tsz = 1;
foreach (var kvInfo in yamlGridInfo)
{
var key = kvInfo.Key.ToString();
var val = kvInfo.Value.ToString();
if (key == "chunksize")
csz = ushort.Parse(val);
else if (key == "tilesize")
tsz = ushort.Parse(val);
else if (key == "snapsize")
continue; // obsolete
}
var grid = gridComp.AllocMapGrid(csz, tsz);
return grid;
}
private void AttachMapEntities()
{
var mapEntity = _mapManager.GetMapEntityIdOrThrow(TargetMap);
@@ -621,15 +582,21 @@ namespace Robust.Server.Maps
}
}
private void ReadGridSectionIndices()
private void ReadGridSection()
{
// sets up the mapping so the serializer can properly deserialize GridIds.
var yamlGrids = RootNode.GetNode<YamlSequenceNode>("grids");
for (var i = 0; i < yamlGrids.Children.Count; i++)
foreach (var yamlGrid in yamlGrids)
{
_readGridIndices.Add(_mapManager.GenerateGridId(null));
var grid = YamlGridSerializer.DeserializeGrid(
_mapManager, TargetMap,
(YamlMappingNode) yamlGrid["settings"],
(YamlSequenceNode) yamlGrid["chunks"],
_tileMap!,
_tileDefinitionManager
);
Grids.Add(grid);
}
}
@@ -645,7 +612,7 @@ namespace Robust.Server.Maps
if (!MapIsPostInit)
{
_mapManager.AddUninitializedMap(TargetMap);
_pauseManager.AddUninitializedMap(TargetMap);
}
}
}
@@ -752,7 +719,7 @@ namespace Robust.Server.Maps
var isPostInit = false;
foreach (var grid in Grids)
{
if (_mapManager.IsMapInitialized(grid.ParentMapId))
if (_pauseManager.IsMapInitialized(grid.ParentMapId))
{
isPostInit = true;
break;
@@ -961,13 +928,6 @@ namespace Robust.Server.Maps
return true;
}
[Virtual]
public class MapLoadException : Exception
{
public MapLoadException(string? message)
: base(message) { }
}
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies,
bool skipHook,
@@ -976,18 +936,19 @@ namespace Robust.Server.Maps
// This is the code that deserializes the Grids index into the GridId. This has to happen between Grid allocation
// and when grids are bound to their entities.
if (node.Value == "null")
{
throw new MapLoadException($"Error in map file: found local grid ID '{node.Value}' which does not exist.");
}
if (node.Value == "null") return new DeserializedValue<GridId>(GridId.Invalid);
var val = int.Parse(node.Value);
if (val >= _readGridIndices.Count)
if (val >= Grids.Count)
{
throw new MapLoadException($"Error in map file: found local grid ID '{val}' which does not exist.");
Logger.ErrorS("map", "Error in map file: found local grid ID '{0}' which does not exist.", val);
}
else
{
return new DeserializedValue<GridId>(Grids[val].Index);
}
return new DeserializedValue<GridId>(_readGridIndices[val]);
return new DeserializedValue<GridId>(GridId.Invalid);
}
ValidationNode ITypeValidator<EntityUid, ValueDataNode>.Validate(ISerializationManager serializationManager,

View File

@@ -36,7 +36,7 @@ namespace Robust.Server.Maps
return gridn;
}
private static YamlNode SerializeChunk(MapChunk chunk)
private static YamlNode SerializeChunk(IMapChunk chunk)
{
var root = new YamlMappingNode();
var value = new YamlScalarNode($"{chunk.X},{chunk.Y}");
@@ -51,7 +51,7 @@ namespace Robust.Server.Maps
return root;
}
private static string SerializeTiles(MapChunk chunk)
private static string SerializeTiles(IMapChunk chunk)
{
// number of bytes written per tile, because sizeof(Tile) is useless.
const int structSize = 4;
@@ -68,8 +68,7 @@ namespace Robust.Server.Maps
{
var tile = chunk.GetTile(x, y);
writer.Write(tile.TypeId);
writer.Write((byte)tile.Flags);
writer.Write(tile.Variant);
writer.Write(tile.Data);
}
}
}
@@ -77,10 +76,38 @@ namespace Robust.Server.Maps
return Convert.ToBase64String(barr);
}
public static void DeserializeChunk(IMapManager mapMan, IMapGridInternal grid,
YamlMappingNode chunkData,
IReadOnlyDictionary<ushort, string> tileDefMapping,
public static MapGrid DeserializeGrid(IMapManagerInternal mapMan, MapId mapId, YamlMappingNode info,
YamlSequenceNode chunks, IReadOnlyDictionary<ushort, string> tileDefMapping,
ITileDefinitionManager tileDefinitionManager)
{
ushort csz = 0;
ushort tsz = 0;
float sgsz = 0.0f;
foreach (var kvInfo in info)
{
var key = kvInfo.Key.ToString();
var val = kvInfo.Value.ToString();
if (key == "chunksize")
csz = ushort.Parse(val);
else if (key == "tilesize")
tsz = ushort.Parse(val);
else if (key == "snapsize")
sgsz = float.Parse(val, CultureInfo.InvariantCulture);
}
//TODO: Pass in options
var grid = mapMan.CreateUnboundGrid(mapId);
foreach (var chunkNode in chunks.Cast<YamlMappingNode>())
{
DeserializeChunk(mapMan, grid, chunkNode, tileDefMapping, tileDefinitionManager);
}
return grid;
}
private static void DeserializeChunk(IMapManager mapMan, IMapGridInternal grid, YamlMappingNode chunkData, IReadOnlyDictionary<ushort, string> tileDefMapping, ITileDefinitionManager tileDefinitionManager)
{
var indNode = chunkData["ind"];
var tileNode = chunkData["tiles"];
@@ -102,13 +129,12 @@ namespace Robust.Server.Maps
for (ushort x = 0; x < grid.ChunkSize; x++)
{
var id = reader.ReadUInt16();
var flags = (TileRenderFlag)reader.ReadByte();
var variant = reader.ReadByte();
var data = reader.ReadUInt16();
var defName = tileDefMapping[id];
id = tileDefinitionManager[defName].TileId;
var tile = new Tile(id, flags, variant);
var tile = new Tile(id, data);
chunk.SetTile(x, y, tile);
}
}

View File

@@ -209,7 +209,7 @@ namespace Robust.Server.Placement
{
EntityCoordinates start = msg.EntityCoordinates;
Vector2 rectSize = msg.RectSize;
foreach (EntityUid entity in EntitySystem.Get<EntityLookupSystem>().GetEntitiesIntersecting(start.GetMapId(_entityManager),
foreach (EntityUid entity in IoCManager.Resolve<IEntityLookup>().GetEntitiesIntersecting(start.GetMapId(_entityManager),
new Box2(start.Position, start.Position + rectSize)))
{
if (_entityManager.Deleted(entity) || _entityManager.HasComponent<IMapGridComponent>(entity) || _entityManager.HasComponent<ActorComponent>(entity))

View File

@@ -10,12 +10,12 @@
</PropertyGroup>
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
<!-- -->
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.7" />
<PackageReference Include="prometheus-net" Version="4.1.1" />
<PackageReference Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
<PackageReference Include="Microsoft.Extensions.Primitives" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Primitives" Version="5.0.0" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -50,6 +50,7 @@ namespace Robust.Server
IoCManager.Register<IMapManagerInternal, NetworkedMapManager>();
IoCManager.Register<INetworkedMapManager, NetworkedMapManager>();
IoCManager.Register<IEntityManager, ServerEntityManager>();
IoCManager.Register<IEntityLookup, EntityLookup>();
IoCManager.Register<IEntityNetworkManager, ServerEntityManager>();
IoCManager.Register<IServerEntityNetworkManager, ServerEntityManager>();
IoCManager.Register<IMapLoader, MapLoader>();

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -135,17 +135,5 @@ namespace Robust.Shared.Maths
{
return $"({Left}, {Bottom}, {Right}, {Top})";
}
/// <summary>
/// Multiplies each side of the box by the scalar.
/// </summary>
public Box2i Scale(int scalar)
{
return new Box2i(
Left * scalar,
Bottom * scalar,
Right * scalar,
Top * scalar);
}
}
}

View File

@@ -8,7 +8,7 @@
</PropertyGroup>
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
<PackageReference Condition="'$(TargetFramework)' == 'net472'" Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.2" />
</ItemGroup>
</Project>

View File

@@ -130,12 +130,6 @@ namespace Robust.Shared.Maths
return new((int) MathF.Floor(X), (int) MathF.Floor(Y));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Vector2i Ceiled()
{
return new((int) MathF.Ceiling(X), (int) MathF.Ceiling(Y));
}
/// <summary>
/// Subtracts a vector from another, returning a new vector.
/// </summary>

View File

@@ -10,10 +10,10 @@
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="3.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.8.0" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>

View File

@@ -128,13 +128,13 @@ namespace Robust.Shared
/// The amount of new entities that can be sent to a client in a single game state, under PVS.
/// </summary>
public static readonly CVarDef<int> NetPVSNewEntityBudget =
CVarDef.Create("net.pvs_new_budget", 20, CVar.ARCHIVE | CVar.REPLICATED);
CVarDef.Create("net.pvs_new_budget", 20, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// The amount of entered entities that can be sent to a client in a single game state, under PVS.
/// The amount of entity updates that can be sent to a client in a single game state, under PVS.
/// </summary>
public static readonly CVarDef<int> NetPVSEntityBudget =
CVarDef.Create("net.pvs_budget", 50, CVar.ARCHIVE | CVar.REPLICATED);
CVarDef.Create("net.pvs_budget", 50, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// Log late input messages from clients.
@@ -1006,17 +1006,6 @@ namespace Robust.Shared
public static readonly CVarDef<bool> ResTexturePreloadCache =
CVarDef.Create("res.texture_preload_cache", true, CVar.CLIENTONLY);
/// <summary>
/// Override seekability of resource streams returned by ResourceManager.
/// See <see cref="ContentPack.StreamSeekMode"/> for int values.
/// </summary>
/// <remarks>
/// This is intended to be a debugging tool primarily.
/// Non-default seek modes WILL result in worse performance.
/// </remarks>
public static readonly CVarDef<int> ResStreamSeekMode =
CVarDef.Create("res.stream_seek_mode", (int)ContentPack.StreamSeekMode.None);
/*
* DEBUG
*/

View File

@@ -20,16 +20,6 @@ namespace Robust.Shared.Configuration
/// </summary>
void SetupNetworking();
/// <summary>
/// Get a replicated client CVar for a specific client.
/// </summary>
/// <typeparam name="T">CVar type.</typeparam>
/// <param name="channel">channel of the connected client.</param>
/// <param name="definition">The CVar.</param>
/// <returns>Replicated CVar of the client.</returns>
public T GetClientCVar<T>(INetChannel channel, CVarDef<T> definition) where T : notnull =>
GetClientCVar<T>(channel, definition.Name);
/// <summary>
/// Get a replicated client CVar for a specific client.
/// </summary>

View File

@@ -1,81 +0,0 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Robust.Shared.Console;
public sealed class CommandBuffer
{
private const string DelayMarker = "-DELAY-";
private int _tickrate = 0;
private int _delay = 0;
private readonly LinkedList<string> _commandBuffer = new();
public void Append(string command)
{
_commandBuffer.AddLast(command);
}
public void Insert(string command)
{
_commandBuffer.AddFirst(command);
}
public void Tick(byte tickRate)
{
_tickrate = tickRate;
if (_delay > 0)
{
_delay -= 1;
}
}
public bool TryGetCommand([MaybeNullWhen(false)]out string command)
{
var next = _commandBuffer.First;
if (next is null) // nothing to do here
{
command = null;
return false;
}
if (next.Value.Equals(DelayMarker))
{
if (_delay == 0) // just finished
{
_commandBuffer.RemoveFirst();
return TryGetCommand(out command);
}
else // currently counting down delay
{
command = null;
return false;
}
}
if (next.Value.StartsWith("wait "))
{
var sTicks = next.Value.Substring(5);
_commandBuffer.RemoveFirst();
if (string.IsNullOrWhiteSpace(sTicks) || !int.TryParse(sTicks, out var ticks)) // messed up command
{
return TryGetCommand(out command);
}
// Setup Timing
_commandBuffer.AddFirst(DelayMarker);
_delay = ticks;
command = null;
return false;
}
// normal command
_commandBuffer.RemoveFirst();
command = next.Value;
return true;
}
}

View File

@@ -48,7 +48,7 @@ namespace Robust.Shared.Console.Commands
continue;
}
shell.ConsoleHost.AppendCommand(line);
shell.ExecuteCommand(line);
}
}
}

View File

@@ -7,7 +7,6 @@ using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Reflection;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Console
@@ -18,16 +17,13 @@ namespace Robust.Shared.Console
protected const string SawmillName = "con";
[Dependency] protected readonly ILogManager LogManager = default!;
[Dependency] private readonly IReflectionManager ReflectionManager = default!;
[Dependency] protected readonly IReflectionManager ReflectionManager = default!;
[Dependency] protected readonly INetManager NetManager = default!;
[Dependency] private readonly IDynamicTypeFactoryInternal _typeFactory = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[ViewVariables]
protected readonly Dictionary<string, IConsoleCommand> AvailableCommands = new();
private readonly CommandBuffer _commandBuffer = new CommandBuffer();
/// <inheritdoc />
public bool IsServer => NetManager.IsServer;
@@ -120,36 +116,6 @@ namespace Robust.Shared.Console
ExecuteCommand(null, command);
}
/// <inheritdoc />
public void AppendCommand(string command)
{
_commandBuffer.Append(command);
}
/// <inheritdoc />
public void InsertCommand(string command)
{
_commandBuffer.Insert(command);
}
/// <inheritdoc />
public void CommandBufferExecute()
{
_commandBuffer.Tick(_timing.TickRate);
while (_commandBuffer.TryGetCommand(out var cmd))
{
try
{
ExecuteCommand(cmd);
}
catch (Exception e)
{
LocalShell.WriteError(e.Message);
}
}
}
/// <summary>
/// A console command that was registered inline through <see cref="IConsoleHost"/>.
/// </summary>

View File

@@ -81,35 +81,11 @@ namespace Robust.Shared.Console
IConsoleShell GetSessionShell(ICommonSession session);
/// <summary>
/// Execute a command string immediately on the local shell, bypassing the command buffer completely.
/// Execute a command string on the local shell.
/// </summary>
/// <param name="command">Command string to execute.</param>
void ExecuteCommand(string command);
/// <summary>
/// Appends a command into the end of the command buffer on the local shell.
/// </summary>
/// <remarks>
/// This command will be ran *sometime* in the future, depending on how many waits are in the buffer.
/// </remarks>
/// <param name="command">Command string to execute.</param>
void AppendCommand(string command);
/// <summary>
/// Inserts a command into the front of the command buffer on the local shell.
/// </summary>
/// <remarks>
/// This command will preempt the next command executed in the command buffer.
/// </remarks>
/// <param name="command">Command string to execute.</param>
void InsertCommand(string command);
/// <summary>
/// Processes any contents of the command buffer on the local shell. This needs to be called regularly (once a tick),
/// inside the simulation. Pausing the server should prevent the buffer from being processed.
/// </summary>
void CommandBufferExecute();
/// <summary>
/// Executes a command string on this specific session shell. If the command does not exist, the command will be forwarded
/// to the

View File

@@ -81,7 +81,6 @@ namespace Robust.Shared.Containers
// spatially move the object to the location of the container. If you don't want this functionality, the
// calling code can save the local position before calling this function, and apply it afterwords.
transform.LocalPosition = Vector2.Zero;
transform.LocalRotation = Angle.Zero;
return true;
}

View File

@@ -209,43 +209,6 @@ namespace Robust.Shared.Containers
return false;
}
/// <summary>
/// Gets the top-most container in the hierarchy for this entity, if it exists.
/// </summary>
public bool TryGetOuterContainer(EntityUid uid, TransformComponent xform, [NotNullWhen(true)] out IContainer? container)
{
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
return TryGetOuterContainer(uid, xform, out container, xformQuery);
}
public bool TryGetOuterContainer(EntityUid uid, TransformComponent xform,
[NotNullWhen(true)] out IContainer? container, EntityQuery<TransformComponent> xformQuery)
{
container = null;
if (!uid.IsValid())
return false;
var conQuery = EntityManager.GetEntityQuery<ContainerManagerComponent>();
var child = uid;
var parent = xform.ParentUid;
while (parent.IsValid())
{
if (conQuery.TryGetComponent(parent, out var conManager) &&
conManager.TryGetContainer(child, out var parentContainer))
{
container = parentContainer;
}
var parentXform = xformQuery.GetComponent(parent);
child = parent;
parent = parentXform.ParentUid;
}
return container != null;
}
#endregion
// Eject entities from their parent container if the parent change is done by the transform only.

View File

@@ -111,7 +111,7 @@ namespace Robust.Shared.ContentPack
var fullStopwatch = Stopwatch.StartNew();
var resolver = CreateResolver();
using var peReader = ModLoader.MakePEReader(assembly, leaveOpen: true);
using var peReader = new PEReader(assembly, PEStreamOptions.LeaveOpen);
var reader = peReader.GetMetadataReader();
var asmName = reader.GetString(reader.GetAssemblyDefinition().Name);
@@ -853,13 +853,13 @@ namespace Robust.Shared.ContentPack
continue;
}
return ModLoader.MakePEReader(File.OpenRead(path));
return ReaderFromStream(File.OpenRead(path));
}
var extraStream = _parent.ExtraRobustLoader?.Invoke(dllName);
if (extraStream != null)
{
return ModLoader.MakePEReader(extraStream);
return ReaderFromStream(extraStream);
}
foreach (var resLoadPath in _resLoadPaths)
@@ -867,7 +867,7 @@ namespace Robust.Shared.ContentPack
try
{
var path = resLoadPath / dllName;
return ModLoader.MakePEReader(_parent._res.ContentFileRead(path));
return ReaderFromStream(_parent._res.ContentFileRead(path));
}
catch (FileNotFoundException)
{
@@ -877,6 +877,23 @@ namespace Robust.Shared.ContentPack
return null;
}
private static PEReader ReaderFromStream(Stream stream)
{
if (OperatingSystem.IsLinux() && stream is FileStream)
{
// PEReader is bugged on Linux and not properly thread safe when doing memory mapping.
// As such, we never pass it a file stream so it uses a different, non-bugged code path.
// See https://github.com/dotnet/runtime/issues/60545
var ms = new MemoryStream();
stream.CopyTo(ms);
ms.Seek(0, SeekOrigin.Begin);
stream.Dispose();
stream = ms;
}
return new PEReader(stream);
}
public PEReader? Resolve(string simpleName)
{
return _dictionary.GetOrAdd(simpleName, ResolveCore);

View File

@@ -1,146 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using Robust.Shared.Utility;
namespace Robust.Shared.ContentPack;
/// <summary>
/// A content root stored in memory, backed by a dictionary.
/// </summary>
public sealed class MemoryContentRoot : IContentRoot, IDisposable
{
private readonly Dictionary<ResourcePath, byte[]> _files = new();
private readonly ReaderWriterLockSlim _lock = new();
/// <summary>
/// Adds a file to the content root, or updates it if that path already exists.
/// </summary>
/// <param name="relPath">The relative path of the file.</param>
/// <param name="data">The data byte array to store in the content root. Stored as is, without being copied or cloned.</param>
public void AddOrUpdateFile(ResourcePath relPath, byte[] data)
{
// Just in case, we ensure it's a clean relative path.
relPath = relPath.Clean().ToRelativePath();
_lock.EnterWriteLock();
try
{
_files[relPath] = data;
}
finally
{
_lock.ExitWriteLock();
}
}
/// <summary>
/// Remove a file from this content root.
/// </summary>
/// <param name="relPath">The relative path to the file.</param>
/// <returns></returns>
public bool RemoveFile(ResourcePath relPath)
{
_lock.EnterWriteLock();
try
{
return _files.Remove(relPath);
}
finally
{
_lock.ExitWriteLock();
}
}
/// <inheritdoc />
public bool TryGetFile(ResourcePath relPath, [NotNullWhen(true)] out Stream? stream)
{
_lock.EnterReadLock();
try
{
if (!_files.TryGetValue(relPath, out var data))
{
stream = null;
return false;
}
// Non-writable stream, as this needs to be thread-safe.
stream = new MemoryStream(data, false);
return true;
}
finally
{
_lock.ExitReadLock();
}
}
/// <inheritdoc />
public IEnumerable<ResourcePath> FindFiles(ResourcePath path)
{
_lock.EnterReadLock();
try
{
foreach (var (file, _) in _files)
{
if (file.TryRelativeTo(path, out _))
yield return file;
}
}
finally
{
_lock.ExitReadLock();
}
}
/// <inheritdoc />
public IEnumerable<string> GetRelativeFilePaths()
{
_lock.EnterReadLock();
try
{
foreach (var (file, _) in _files)
{
yield return file.ToString();
}
}
finally
{
_lock.ExitReadLock();
}
}
/// <summary>
/// Enumerates all files and their resource paths on this content root.
/// </summary>
/// <remarks>Do not modify or keep around the returned byte array, it's meant to be read-only.</remarks>
public IEnumerable<(ResourcePath relPath, byte[] data)> GetAllFiles()
{
_lock.EnterReadLock();
try
{
foreach (var (p, d) in _files)
{
yield return (p, d);
}
}
finally
{
_lock.ExitReadLock();
}
}
/// <inheritdoc />
public void Mount()
{
// Nada. We don't need to perform any special logic here.
}
/// <inheritdoc />
public void Dispose()
{
_lock.Dispose();
}
}

View File

@@ -160,7 +160,7 @@ namespace Robust.Shared.ContentPack
private static (string[] refs, string name) GetAssemblyReferenceData(Stream stream)
{
using var reader = ModLoader.MakePEReader(stream);
using var reader = new PEReader(stream);
var metaReader = reader.GetMetadataReader();
var name = metaReader.GetString(metaReader.GetAssemblyDefinition().Name);
@@ -383,13 +383,5 @@ namespace Robust.Shared.ContentPack
EngineModuleDirectories = _engineModuleDirectories.ToArray()
};
}
internal static PEReader MakePEReader(Stream stream, bool leaveOpen=false)
{
if (!stream.CanSeek)
stream = leaveOpen ? stream.CopyToMemoryStream() : stream.ConsumeToMemoryStream();
return new PEReader(stream, leaveOpen ? PEStreamOptions.LeaveOpen : default);
}
}
}

View File

@@ -1,95 +0,0 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Robust.Shared.ContentPack;
internal sealed class NonSeekableStream : Stream
{
private readonly Stream _baseStream;
public NonSeekableStream(Stream baseStream)
{
_baseStream = baseStream;
}
protected override void Dispose(bool disposing)
{
if (disposing)
_baseStream.Dispose();
}
public override ValueTask DisposeAsync()
{
return _baseStream.DisposeAsync();
}
public override void Flush()
{
_baseStream.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
return _baseStream.Read(buffer, offset, count);
}
public override int Read(Span<byte> buffer)
{
return _baseStream.Read(buffer);
}
public override int ReadByte()
{
return _baseStream.ReadByte();
}
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = new CancellationToken())
{
return _baseStream.ReadAsync(buffer, cancellationToken);
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
_baseStream.Write(buffer, offset, count);
}
public override void Write(ReadOnlySpan<byte> buffer)
{
_baseStream.Write(buffer);
}
public override void WriteByte(byte value)
{
_baseStream.WriteByte(value);
}
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = new CancellationToken())
{
return _baseStream.WriteAsync(buffer, cancellationToken);
}
public override bool CanRead => _baseStream.CanRead;
public override bool CanSeek => false;
public override bool CanWrite => _baseStream.CanWrite;
// .NET mingles seekability and exposing length.
// This makes absolutely no sense but ok.
public override long Length => throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
}

View File

@@ -23,8 +23,6 @@ namespace Robust.Shared.ContentPack
private readonly List<(ResourcePath prefix, IContentRoot root)> _contentRoots =
new();
private StreamSeekMode _streamSeekMode;
// Special file names on Windows like serial ports.
private static readonly Regex BadPathSegmentRegex =
new("^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$", RegexOptions.IgnoreCase);
@@ -47,8 +45,6 @@ namespace Robust.Shared.ContentPack
{
UserData = new VirtualWritableDirProvider();
}
_config.OnValueChanged(CVars.ResStreamSeekMode, i => _streamSeekMode = (StreamSeekMode)i, true);
}
/// <inheritdoc />
@@ -195,9 +191,8 @@ namespace Robust.Shared.ContentPack
continue;
}
if (root.TryGetFile(relative, out var stream))
if (root.TryGetFile(relative, out fileStream))
{
fileStream = WrapStream(stream);
return true;
}
}
@@ -211,35 +206,6 @@ namespace Robust.Shared.ContentPack
}
}
/// <summary>
/// Apply <see cref="_streamSeekMode"/> to the provided stream.
/// </summary>
private Stream WrapStream(Stream stream)
{
switch (_streamSeekMode)
{
case StreamSeekMode.None:
return stream;
case StreamSeekMode.ForceSeekable:
if (stream.CanSeek)
return stream;
var ms = new MemoryStream(stream.CopyToArray(), writable: false);
stream.Dispose();
return ms;
case StreamSeekMode.ForceNonSeekable:
if (!stream.CanSeek)
return stream;
return new NonSeekableStream(stream);
default:
throw new InvalidOperationException();
}
}
/// <inheritdoc />
public bool ContentFileExists(string path)
{

View File

@@ -689,7 +689,6 @@ Types:
CancellationToken: { All: True }
CancellationTokenSource: { All: True }
Interlocked: { All: True }
Monitor: { All: True }
System.Web:
HttpUtility:
Methods:

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