Compare commits

..

1 Commits
v0.7.3 ... doom

Author SHA1 Message Date
PJB3005
ee330d0ae9 Make DOOM work
I think I lost this work originally
2025-01-15 23:08:44 +01:00
787 changed files with 13793 additions and 33502 deletions

52
.appveyor.yml Normal file
View File

@@ -0,0 +1,52 @@
environment:
sonarqubekey:
secure: h3llq6OeVa94hJ71UOEQSQDq75vFt+doso7iFry0gvt/fFcyeonY9wY+ETOIVITK
global:
PYTHONUNBUFFERED: True
HEADLESS: 1 # For the unit tests.
version: 0.1.0.{build}
pull_requests:
do_not_increment_build_number: true
image: Visual Studio 2019
install:
- ps: >
if (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER -And $env:APPVEYOR_REPO_BRANCH -Eq "master")
{
cinst msbuild-sonarqube-runner;
}
before_build:
- cmd: py -3.5 -m pip install --user requests
- cmd: git submodule update --init --recursive
- ps: >
if (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER -And $env:APPVEYOR_REPO_BRANCH -Eq "master")
{
SonarScanner.MSBuild.exe begin /k:"ss14" /d:"sonar.host.url=https://sonarcloud.io" /d:"sonar.login=$env:sonarqubekey" /o:"space-wizards" /d:sonar.cs.nunit.reportsPaths="$(Get-Location)\nunitTestResult.xml";
}
platform: x64
configuration: Debug
cache:
- packages -> **\*.csproj
- Dependencies
build:
project: RobustToolbox.sln
parallel: false
verbosity: minimal
build_script:
- ps: dotnet build RobustToolbox.sln /p:AppVeyor=yes
test_script:
- ps: dotnet test Robust.UnitTesting/Robust.UnitTesting.csproj
after_test:
- ps: >
if (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER -And $env:APPVEYOR_REPO_BRANCH -Eq "master")
{
SonarScanner.MSBuild.exe end /d:"sonar.login=$env:sonarqubekey";
}

View File

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

View File

@@ -42,7 +42,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.100
dotnet-version: 5.0.100
- name: Build
run: dotnet build

View File

@@ -23,7 +23,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.100
dotnet-version: 5.0.100
- name: Package client
run: Tools/package_client_build.py -p windows mac linux

View File

@@ -21,7 +21,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.100
dotnet-version: 5.0.100
- name: Disable submodule autoupdate
run: touch BuildChecker/DISABLE_SUBMODULE_AUTOUPDATE
@@ -38,4 +38,4 @@ jobs:
- name: Content.Tests
run: dotnet test --no-build Content.Tests/Content.Tests.csproj -v n
- name: Content.IntegrationTests
run: COMPlus_gcServer=1 dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n
run: dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n

6
.gitmodules vendored
View File

@@ -13,9 +13,3 @@
[submodule "ManagedHttpListener"]
path = ManagedHttpListener
url = https://github.com/space-wizards/ManagedHttpListener.git
[submodule "Linguini"]
path = Linguini
url = https://github.com/space-wizards/Linguini
[submodule "cefglue"]
path = cefglue
url = https://github.com/space-wizards/cefglue.git

View File

@@ -3,10 +3,10 @@
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<DefineConstants Condition="'$(TargetFramework)' == 'netstandard2.1' or '$(TargetFramework)' == 'netcoreapp3.1'">$(DefineConstants);HAS_FULL_SPAN</DefineConstants>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<DefaultItemExcludes>Lidgren.Network/**/*</DefaultItemExcludes>
<DefineConstants>$(DefineConstants);USE_RELEASE_STATISTICS</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>

Submodule Linguini deleted from b3c05c2f31

View File

@@ -1,8 +1,8 @@
<Project>
<!-- Engine-specific properties. Content should not use this file. -->
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10</LangVersion>
<TargetFramework>net5.0</TargetFramework>
<LangVersion>9</LangVersion>
<Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
</PropertyGroup>

View File

@@ -26,7 +26,7 @@
<TargetOS Condition="'$(TargetOS)' == ''">$(ActualOS)</TargetOS>
<Python>python3</Python>
<Python Condition="'$(ActualOS)' == 'Windows'">py -3</Python>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<EnableClientScripting>True</EnableClientScripting>
<!-- Client scripting is disabled on full release builds for security and size reasons. -->
<EnableClientScripting Condition="'$(FullRelease)' == 'True'">False</EnableClientScripting>

View File

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

View File

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

View File

@@ -23,12 +23,12 @@ namespace OpenToolkit.GraphicsLibraryFramework
return IntPtr.Zero;
}
if (OperatingSystem.IsLinux())
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return NativeLibrary.Load("libglfw.so.3", assembly, path);
}
if (OperatingSystem.IsMacOS())
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return NativeLibrary.Load("libglfw.3.dylib", assembly, path);
}
@@ -406,11 +406,5 @@ namespace OpenToolkit.GraphicsLibraryFramework
[DllImport(LibraryName)]
public static extern uint glfwGetX11Window(Window* window);
[DllImport(LibraryName)]
public static extern IntPtr glfwGetX11Display(Window* window);
[DllImport(LibraryName)]
public static extern IntPtr glfwGetWin32Window(Window* window);
}
}

View File

@@ -23,48 +23,6 @@
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- name: Box2D
license: |
MIT License
Copyright (c) 2019 Erin Catto
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.
- name: Bullet Physics SDK
license: |
The files in this repository are licensed under the zlib license, except for the files under 'Extras' and examples/ThirdPartyLibs.
Bullet Continuous Collision Detection and Physics Library
http://bulletphysics.org
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
- name: Castle Core
license: |
Copyright 2004-2016 Castle Project - http://www.castleproject.org/
@@ -359,43 +317,6 @@
See the License for the specific language governing permissions and
limitations under the License.
- name: Farseer Physics Engine
license: |
Microsoft Permissive License (Ms-PL)
This license governs use of the accompanying software.
If you use the software, you accept this license.
If you do not accept the license, do not use the software.
1. Definitions
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
A "contribution" is the original software, or any additions or changes to the software.
A "contributor" is any person that distributes its contribution under this license.
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
2. Grant of Rights
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution,
prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to
make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or
derivative works of the contribution in the software.
3. Conditions and Limitations
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
your patent license from such contributor to the software ends automatically.
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark,
and attribution notices that are present in the software.
(D) If you distribute any portion of the software in source code form, you may do so only under this license by
including a complete copy of this license with your distribution. If you distribute any portion of the software in
compiled or object code form, you may only do so under a license that complies with this license.
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties,
guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change.
To the extent permitted under your local laws, the contributors exclude the implied warranties of
merchantability, fitness for a particular purpose and non-infringement.
- name: Mono.Cecil
license: |
Copyright (c) 2008 - 2015 Jb Evain

View File

@@ -0,0 +1 @@
console-line-edit-placeholder = Command Here

View File

@@ -1,11 +0,0 @@
## EntitySpawnWindow
entity-spawn-window-title = Entity Spawn Panel
entity-spawn-window-search-bar-placeholder = search
entity-spawn-window-clear-button = Clear
entity-spawn-window-erase-button-text = Erase Mode
entity-spawn-window-override-menu-tooltip = Override placement
## Console
console-line-edit-placeholder = Command Here

View File

@@ -1 +0,0 @@
tab-container-not-tab-title-provided = No title

View File

@@ -1,11 +0,0 @@
## ViewVariablesInstanceEntity
view-variable-instance-entity-server-components-add-component-button-placeholder = Add Component
view-variable-instance-entity-client-variables-tab-title = Client Variables
view-variable-instance-entity-client-components-tab-title = Client Components
view-variable-instance-entity-server-variables-tab-title = Server Variables
view-variable-instance-entity-server-components-tab-title = Server Components
view-variable-instance-entity-client-components-search-bar-placeholder = Search
view-variable-instance-entity-server-components-search-bar-placeholder = Search
view-variable-instance-entity-add-window-server-components = Add Component [S]
view-variable-instance-entity-add-window-client-components = Add Component [C]

View File

@@ -2,15 +2,29 @@ preset raw;
#include "/Shaders/Internal/shadow_cast_shared.swsl"
#include "/Shaders/Internal/fov_shared.swsl"
const highp float g_MinVariance = 0.0;
varying highp vec2 worldPosition;
// Center of the FOV, in world coordinates.
uniform highp vec2 center;
void vertex()
{
highp vec3 transformed = modelMatrix * vec3(VERTEX, 1.0);
worldPosition = transformed.xy;
transformed = projectionMatrix * viewMatrix * transformed;
VERTEX = transformed.xy;
}
void fragment()
{
highp float ourDist = length(worldSpaceDiff);
highp vec2 diff = worldPosition - center;
highp vec2 occlDist = occludeDepth(worldSpaceDiff, TEXTURE, 0.25);
highp float ourDist = length(diff);
highp vec2 occlDist = occludeDepth(diff, TEXTURE, 0.25);
highp float occlusion = ChebyshevUpperBound(occlDist, ourDist);

View File

@@ -2,18 +2,32 @@ preset raw;
#include "/Shaders/Internal/shadow_cast_shared.swsl"
#include "/Shaders/Internal/fov_shared.swsl"
const highp float g_MinVariance = 0.0;
varying highp vec2 worldPosition;
// Center of the FOV, in world coordinates.
uniform highp vec2 center;
void vertex()
{
highp vec3 transformed = modelMatrix * vec3(VERTEX, 1.0);
worldPosition = transformed.xy;
transformed = projectionMatrix * viewMatrix * transformed;
VERTEX = transformed.xy;
}
void fragment()
{
highp float ourDist = length(worldSpaceDiff);
highp vec2 diff = worldPosition - center;
highp float occlDist = occludeDepth(worldSpaceDiff, TEXTURE, 0.75).r;
highp float ourDist = length(diff);
highp float occlDist = occludeDepth(diff, TEXTURE, 0.75).r;
// *Very* simple biased shadow check for FOV.
if (!doesOcclude(worldSpaceDiff, TEXTURE, 0.75, -0.75/32.0))
if (!doesOcclude(diff, TEXTURE, 0.75, -0.75/32.0))
{
discard;
}

View File

@@ -1,16 +0,0 @@
// Shared between fov-lighting.swsl and fov.swsl, which both use the Clyde quad,
// manually transformed into clip-space to cover the entire viewport
// World-space position offset from centre to pixel.
varying highp vec2 worldSpaceDiff;
// Inverted transformation matrix from clip coordinates to difference coordinates.
uniform highp mat3 clipToDiff;
void vertex()
{
// Convert quad-space (0.0 to 1.0) to clip-space (-1.0 to 1.0)
VERTEX = (VERTEX.xy - 0.5) * 2.0;
worldSpaceDiff = (clipToDiff * vec3(VERTEX, 1.0)).xy;
}

View File

@@ -1,41 +0,0 @@
preset raw;
uniform highp vec2 direction;
uniform highp vec2 size;
uniform highp float radius;
varying highp vec2 pos;
varying highp vec4 blurPos1;
varying highp vec4 blurPos2;
void vertex()
{
highp float aspect = size.y / size.x;
highp float horRadius = aspect * radius;
highp vec2 offset = vec2(horRadius, radius) * direction;
VERTEX = apply_mvp(VERTEX);
pos = (VERTEX + vec2(1.0)) / 2.0;
blurPos1.xy = pos + offset;
blurPos1.zw = pos - offset;
blurPos2.xy = pos + offset * 2.0;
blurPos2.zw = pos - offset * 2.0;
}
void fragment()
{
// Very simple gaussian blur.
highp vec4 sum = zTexture(pos) * 0.375;
sum += zTexture(blurPos1.xy) * 0.25;
sum += zTexture(blurPos1.zw) * 0.25;
sum += zTexture(blurPos2.xy) * 0.0625;
sum += zTexture(blurPos2.zw) * 0.0625;
COLOR = sum;
}

View File

@@ -40,7 +40,7 @@ void fragment()
highp vec2 diff = worldPosition - lightCenter;
// Totally not hacky PCF on top of VSM.
highp float occlusion = lightIndex < 0.0 ? 1.0 : createOcclusion(diff);
highp float occlusion = createOcclusion(diff);
if (occlusion == 0.0)
{

View File

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

View File

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

View File

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

View File

@@ -4,11 +4,9 @@ namespace Robust.Benchmarks
{
internal class Program
{
// --allCategories=ctg1,ctg2
// --anyCategories=ctg1,ctg2
public static void Main(string[] args)
{
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run();
}
}
}

View File

@@ -1,40 +0,0 @@
using System.Globalization;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Benchmarks.Serialization
{
public class BenchmarkIntSerializer : ITypeSerializer<int, ValueDataNode>
{
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, ISerializationContext? context = null)
{
return int.TryParse(node.Value, out _)
? new ValidatedValueNode(node)
: new ErrorNode(node, $"Failed parsing int value: {node.Value}");
}
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
{
return new DeserializedValue<int>(int.Parse(node.Value, CultureInfo.InvariantCulture));
}
public DataNode Write(ISerializationManager serializationManager, int value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
return new ValueDataNode(value.ToString(CultureInfo.InvariantCulture));
}
public int Copy(ISerializationManager serializationManager, int source, int target, bool skipHook,
ISerializationContext? context = null)
{
return source;
}
}
}

View File

@@ -6,13 +6,11 @@ using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Robust.Benchmarks.Serialization.Copy
{
[MemoryDiagnoser]
public class SerializationCopyBenchmark : SerializationBenchmark
{
public SerializationCopyBenchmark()
@@ -37,10 +35,6 @@ namespace Robust.Benchmarks.Serialization.Copy
private SeedDataDefinition Seed { get; }
private BenchmarkFlagsEnum FlagZero = BenchmarkFlagsEnum.Zero;
private BenchmarkFlagsEnum FlagThirtyOne = BenchmarkFlagsEnum.ThirtyOne;
[Benchmark]
public string? CreateCopyString()
{
@@ -117,35 +111,5 @@ namespace Robust.Benchmarks.Serialization.Copy
return copy;
}
[Benchmark]
[BenchmarkCategory("flag")]
public object? CopyFlagZero()
{
return SerializationManager.CopyWithTypeSerializer(
typeof(FlagSerializer<BenchmarkFlags>),
(int) FlagZero,
(int) FlagZero);
}
[Benchmark]
[BenchmarkCategory("flag")]
public object? CopyFlagThirtyOne()
{
return SerializationManager.CopyWithTypeSerializer(
typeof(FlagSerializer<BenchmarkFlags>),
(int) FlagThirtyOne,
(int) FlagThirtyOne);
}
[Benchmark]
[BenchmarkCategory("customTypeSerializer")]
public object? CopyIntegerCustomSerializer()
{
return SerializationManager.CopyWithTypeSerializer(
typeof(BenchmarkIntSerializer),
Integer,
Integer);
}
}
}

View File

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

View File

@@ -5,7 +5,7 @@ namespace Robust.Benchmarks.Serialization.Definitions
[DataDefinition]
public class DataDefinitionWithString
{
[DataField("string")]
[field: DataField("string")]
public string StringField { get; init; } = default!;
}
}

View File

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

View File

@@ -3,7 +3,6 @@ using Robust.Shared.Serialization.Manager;
namespace Robust.Benchmarks.Serialization.Initialize
{
[MemoryDiagnoser]
public class SerializationInitializeBenchmark : SerializationBenchmark
{
[IterationCleanup]

View File

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

View File

@@ -1,127 +0,0 @@
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Value;
namespace Robust.Benchmarks.Serialization
{
[MemoryDiagnoser]
public class SerializationArrayBenchmark : SerializationBenchmark
{
public SerializationArrayBenchmark()
{
InitializeSerialization();
OneStringDefNode = new SequenceDataNode();
OneStringDefNode.Add(new MappingDataNode
{
["string"] = new ValueDataNode("ABC")
});
TenStringDefsNode = new SequenceDataNode();
for (var i = 0; i < 10; i++)
{
TenStringDefsNode.Add(new MappingDataNode
{
["string"] = new ValueDataNode("ABC")
});
}
}
private SequenceDataNode EmptyNode { get; } = new();
private SequenceDataNode OneIntNode { get; } = new("1");
private SequenceDataNode TenIntsNode { get; } = new("1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
private SequenceDataNode OneStringDefNode { get; }
private SequenceDataNode TenStringDefsNode { get; }
[Benchmark]
[BenchmarkCategory("read")]
public string[]? ReadEmptyString()
{
return SerializationManager.ReadValue<string[]>(EmptyNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public string[]? ReadOneString()
{
return SerializationManager.ReadValue<string[]>(OneIntNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public string[]? ReadTenStrings()
{
return SerializationManager.ReadValue<string[]>(TenIntsNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public int[]? ReadEmptyInt()
{
return SerializationManager.ReadValue<int[]>(EmptyNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public int[]? ReadOneInt()
{
return SerializationManager.ReadValue<int[]>(OneIntNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public int[]? ReadTenInts()
{
return SerializationManager.ReadValue<int[]>(TenIntsNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public DataDefinitionWithString[]? ReadEmptyStringDataDef()
{
return SerializationManager.ReadValue<DataDefinitionWithString[]>(EmptyNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public DataDefinitionWithString[]? ReadOneStringDataDef()
{
return SerializationManager.ReadValue<DataDefinitionWithString[]>(OneStringDefNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public DataDefinitionWithString[]? ReadTenStringDataDefs()
{
return SerializationManager.ReadValue<DataDefinitionWithString[]>(TenStringDefsNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public SealedDataDefinitionWithString[]? ReadEmptySealedStringDataDef()
{
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(EmptyNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public SealedDataDefinitionWithString[]? ReadOneSealedStringDataDef()
{
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(OneStringDefNode);
}
[Benchmark]
[BenchmarkCategory("read")]
public SealedDataDefinitionWithString[]? ReadTenSealedStringDataDefs()
{
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(TenStringDefsNode);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,19 +0,0 @@
using System;
using Microsoft.CodeAnalysis;
namespace Robust.Client.NameGenerator
{
public class InvalidXamlRootTypeException : Exception
{
public readonly INamedTypeSymbol ExpectedType;
public readonly INamedTypeSymbol ExpectedBaseType;
public readonly INamedTypeSymbol Actual;
public InvalidXamlRootTypeException(INamedTypeSymbol actual, INamedTypeSymbol expectedType, INamedTypeSymbol expectedBaseType)
{
Actual = actual;
ExpectedType = expectedType;
ExpectedBaseType = expectedBaseType;
}
}
}

View File

@@ -13,6 +13,5 @@
<Compile Link="XamlX\filename" Include="../XamlX/src/XamlX/**/*.cs" />
<Compile Remove="../XamlX/src/XamlX/**/SreTypeSystem.cs" />
<Compile Remove="../XamlX/src/XamlX/obj/**" />
<Compile Include="..\Robust.Client\UserInterface\ControlPropertyAccess.cs" />
</ItemGroup>
</Project>

View File

@@ -1,12 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using Robust.Client.UserInterface;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
@@ -38,10 +36,9 @@ namespace Robust.Client.AutoGenerated
class NameVisitor : IXamlAstVisitor
{
private readonly List<(string name, string type, AccessLevel access)> _names =
new List<(string name, string type, AccessLevel access)>();
private List<(string name, string type)> _names = new List<(string name, string type)>();
public static List<(string name, string type, AccessLevel access)> GetNames(IXamlAstNode node)
public static List<(string name, string type)> GetNames(IXamlAstNode node)
{
var visitor = new NameVisitor();
node.Visit(visitor);
@@ -58,42 +55,27 @@ namespace Robust.Client.AutoGenerated
{
var clrtype = objectNode.Type.GetClrType();
var isControl = IsControl(clrtype);
//clrtype.Interfaces.Any(i =>
//i.IsInterface && i.FullName == "Robust.Client.UserInterface.IControl");
if (!isControl)
return node;
// Find Name and Access properties
XamlAstTextNode nameText = null;
XamlAstTextNode accessText = null;
foreach (var child in objectNode.Children)
{
if (child is XamlAstXamlPropertyValueNode propertyValueNode &&
propertyValueNode.Property is XamlAstNamePropertyReference namedProperty &&
namedProperty.Name == "Name" &&
propertyValueNode.Values.Count > 0 &&
propertyValueNode.Values[0] is XamlAstTextNode text)
{
switch (namedProperty.Name)
var reg = (text.Text, $@"{clrtype.Namespace}.{clrtype.Name}");
if (!_names.Contains(reg))
{
case "Name":
nameText = text;
break;
case "Access":
accessText = text;
break;
_names.Add(reg);
}
}
}
if (nameText == null)
return node;
var reg = (nameText.Text,
$@"{clrtype.Namespace}.{clrtype.Name}",
accessText != null ? (AccessLevel) Enum.Parse(typeof(AccessLevel), accessText.Text) : AccessLevel.Protected);
if (!_names.Contains(reg))
{
_names.Add(reg);
}
}
return node;
@@ -111,8 +93,7 @@ namespace Robust.Client.AutoGenerated
private static string GenerateSourceCode(
INamedTypeSymbol classSymbol,
string xamlFile,
CSharpCompilation comp,
string fileName)
CSharpCompilation comp)
{
var className = classSymbol.Name;
var nameSpace = classSymbol.ContainingNamespace.ToDisplayString();
@@ -130,48 +111,11 @@ namespace Robust.Client.AutoGenerated
compiler.Transform(parsed);
var initialRoot = (XamlAstObjectNode) parsed.Root;
var names = NameVisitor.GetNames(initialRoot);
var rootType = (INamedTypeSymbol)initialRoot.Type.GetClrType().Id;
var rootTypeString = rootType.ToString();
if (classSymbol.ToString() != rootTypeString && classSymbol.BaseType?.ToString() != rootTypeString)
throw new InvalidXamlRootTypeException(rootType, classSymbol, classSymbol.BaseType);
var namedControls = names.Select(info =>
{
(string name, string type, AccessLevel access) = info;
string accessStr;
switch (access)
{
case AccessLevel.Public:
accessStr = "public";
break;
case AccessLevel.Protected when classSymbol.IsSealed:
case AccessLevel.PrivateProtected when classSymbol.IsSealed:
case AccessLevel.Private:
accessStr = "private";
break;
case AccessLevel.Protected:
accessStr = "protected";
break;
case AccessLevel.PrivateProtected:
accessStr = "private protected";
break;
case AccessLevel.Internal:
case AccessLevel.ProtectedInternal when classSymbol.IsSealed:
accessStr = "internal";
break;
case AccessLevel.ProtectedInternal:
accessStr = "protected internal";
break;
default:
throw new ArgumentException($"Invalid access level \"{Enum.GetName(typeof(AccessLevel), access)}\" " +
$"for control {name} in file {fileName}.");
}
return $" {accessStr} global::{type} {name} => this.FindControl<global::{type}>(\"{name}\");";
});
var fieldAccess = classSymbol.IsSealed ? "private" : "protected";
//var names = NameVisitor.GetNames((XamlAstObjectNode)XDocumentXamlParser.Parse(xamlFile).Root);
var namedControls = names.Select(info => " " +
$"{fieldAccess} global::{info.type} {info.name} => " +
$"this.FindControl<global::{info.type}>(\"{info.name}\");");
return $@"// <auto-generated />
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -185,6 +129,7 @@ namespace {nameSpace}
";
}
public void Execute(GeneratorExecutionContext context)
{
var comp = (CSharpCompilation) context.Compilation;
@@ -202,10 +147,9 @@ namespace {nameSpace}
foreach (var typeSymbol in symbols)
{
var xamlFileName = $"{typeSymbol.Name}.xaml";
var xamlFileNameSep = $"{Path.DirectorySeparatorChar}{xamlFileName}";
var relevantXamlFiles = context.AdditionalFiles.Where(t => t.Path.EndsWith(xamlFileNameSep)).ToArray();
var relevantXamlFile = context.AdditionalFiles.FirstOrDefault(t => t.Path.EndsWith(xamlFileName));
if (relevantXamlFiles.Length == 0)
if (relevantXamlFile == null)
{
context.ReportDiagnostic(
Diagnostic.Create(
@@ -221,28 +165,13 @@ namespace {nameSpace}
continue;
}
if (relevantXamlFiles.Length > 1)
{
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"RXN0002",
$"Found multiple candidate XAML files for {typeSymbol}",
$"Multiple files exist with name {xamlFileName}",
"Usage",
DiagnosticSeverity.Error,
true),
typeSymbol.Locations[0]));
continue;
}
var txt = relevantXamlFiles[0].GetText()?.ToString();
var txt = relevantXamlFile.GetText()?.ToString();
if (txt == null)
{
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"RXN0004",
"RXN0002",
$"Unexpected empty Xaml-File was found at {xamlFileName}",
"Expected Content due to a Class with the same name being annotated with [GenerateTypedNameReferences].",
"Usage",
@@ -255,30 +184,15 @@ namespace {nameSpace}
try
{
var sourceCode = GenerateSourceCode(typeSymbol, txt, comp, xamlFileName);
var sourceCode = GenerateSourceCode(typeSymbol, txt, comp);
context.AddSource($"{typeSymbol.Name}.g.cs", SourceText.From(sourceCode, Encoding.UTF8));
}
catch (InvalidXamlRootTypeException invRootType)
{
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"RXN0005",
$"XAML-File {xamlFileName} has the wrong root type!",
$"{xamlFileName}: Expected root type '{invRootType.ExpectedType}' or '{invRootType.ExpectedBaseType}', but got '{invRootType.Actual}'.",
"Usage",
DiagnosticSeverity.Error,
true),
Location.Create(xamlFileName, new TextSpan(0, 0),
new LinePositionSpan(new LinePosition(0, 0), new LinePosition(0, 0)))));
continue;
}
catch (Exception e)
{
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"RXN0003",
"AXN0003",
"Unhandled exception occured while generating typed Name references.",
$"Unhandled exception occured while generating typed Name references: {e}",
"Usage",
@@ -326,7 +240,7 @@ namespace {nameSpace}
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"RXN0006",
"RXN0004",
missingPartialKeywordMessage,
missingPartialKeywordMessage,
"Usage",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,88 +0,0 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using Robust.Shared.ContentPack;
using Xilium.CefGlue;
namespace Robust.Client.WebView.Cef
{
public static class Program
{
// This was supposed to be the main entry for the subprocess program... It doesn't work.
public static int Main(string[] args)
{
// This is a workaround for this to work on UNIX.
var argv = args;
if (CefRuntime.Platform != CefRuntimePlatform.Windows)
{
argv = new string[args.Length + 1];
Array.Copy(args, 0, argv, 1, args.Length);
argv[0] = "-";
}
/*
if (OperatingSystem.IsLinux())
{
// Chromium tries to load libEGL.so and libGLESv2.so relative to the process executable on Linux.
// (Compared to Windows where it is relative to Chromium's *module*)
// There is a TODO "is this correct?" in the Chromium code for this.
// Great.
//CopyDllToExecutableDir("libEGL.so");
//CopyDllToExecutableDir("libGLESv2.so");
// System.Threading.Thread.Sleep(200000);
}
*/
var mainArgs = new CefMainArgs(argv);
// This will block executing until the subprocess is shut down.
var code = CefRuntime.ExecuteProcess(mainArgs, null, IntPtr.Zero);
if (code != 0)
{
System.Console.WriteLine($"CEF Subprocess exited unsuccessfully with exit code {code}! Arguments: {string.Join(' ', argv)}");
}
return code;
}
/* private static void CopyDllToExecutableDir(string dllName)
{
var executableDir = PathHelpers.GetExecutableDirectory();
var targetPath = Path.Combine(executableDir, dllName);
if (File.Exists(targetPath))
return;
// Find source file.
string? srcFile = null;
foreach (var searchDir in WebViewManagerCef.NativeDllSearchDirectories())
{
var searchPath = Path.Combine(searchDir, dllName);
if (File.Exists(searchPath))
{
srcFile = searchPath;
break;
}
}
if (srcFile == null)
return;
for (var i = 0; i < 5; i++)
{
try
{
if (File.Exists(targetPath))
return;
File.Copy(srcFile, targetPath);
return;
}
catch
{
// Catching race condition lock errors and stuff I guess.
}
}
} */
}
}

View File

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

View File

@@ -1,55 +0,0 @@
using System;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Xilium.CefGlue;
namespace Robust.Client.WebView.Cef
{
internal class RobustCefApp : CefApp
{
private readonly BrowserProcessHandler _browserProcessHandler = new();
private readonly RenderProcessHandler _renderProcessHandler = new();
protected override CefBrowserProcessHandler GetBrowserProcessHandler()
{
return _browserProcessHandler;
}
protected override CefRenderProcessHandler GetRenderProcessHandler()
{
return _renderProcessHandler;
}
protected override void OnBeforeCommandLineProcessing(string processType, CefCommandLine commandLine)
{
// Disable zygote on Linux.
commandLine.AppendSwitch("--no-zygote");
// Work around https://bitbucket.org/chromiumembedded/cef/issues/3213/ozone-egl-initialization-does-not-have
// Desktop GL force makes Chromium not try to load its own ANGLE/Swiftshader so load paths aren't problematic.
if (OperatingSystem.IsLinux())
commandLine.AppendSwitch("--use-gl", "desktop");
// commandLine.AppendSwitch("--single-process");
//commandLine.AppendSwitch("--disable-gpu");
//commandLine.AppendSwitch("--disable-gpu-compositing");
//commandLine.AppendSwitch("--in-process-gpu");
commandLine.AppendSwitch("disable-threaded-scrolling", "1");
commandLine.AppendSwitch("disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents");
if(IoCManager.Instance != null)
Logger.Debug($"{commandLine}");
}
private class BrowserProcessHandler : CefBrowserProcessHandler
{
}
// TODO CEF: Research - Is this even needed?
private class RenderProcessHandler : CefRenderProcessHandler
{
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,101 +0,0 @@
using System;
using System.IO;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Xilium.CefGlue;
namespace Robust.Client.WebView.Cef
{
internal partial class WebViewManagerCef : IWebViewManagerImpl
{
private CefApp _app = default!;
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
public void Initialize()
{
IoCManager.Instance!.InjectDependencies(this, oneOff: true);
string subProcessName;
if (OperatingSystem.IsWindows())
subProcessName = "Robust.Client.WebView.exe";
else if (OperatingSystem.IsLinux())
subProcessName = "Robust.Client.WebView";
else
throw new NotSupportedException("Unsupported platform for CEF!");
var subProcessPath = PathHelpers.ExecutableRelativeFile(subProcessName);
var cefResourcesPath = LocateCefResources();
// System.Console.WriteLine(AppContext.GetData("NATIVE_DLL_SEARCH_DIRECTORIES"));
if (cefResourcesPath == null)
throw new InvalidOperationException("Unable to locate cef_resources directory!");
var settings = new CefSettings()
{
WindowlessRenderingEnabled = true, // So we can render to our UI controls.
ExternalMessagePump = false, // Unsure, honestly. TODO CEF: Research this?
NoSandbox = true, // Not disabling the sandbox crashes CEF.
BrowserSubprocessPath = subProcessPath,
LocalesDirPath = Path.Combine(cefResourcesPath, "locales"),
ResourcesDirPath = cefResourcesPath,
RemoteDebuggingPort = 9222,
};
Logger.Info($"CEF Version: {CefRuntime.ChromeVersion}");
_app = new RobustCefApp();
// We pass no main arguments...
CefRuntime.Initialize(new CefMainArgs(null), settings, _app, IntPtr.Zero);
// TODO CEF: After this point, debugging breaks. No, literally. My client crashes but ONLY with the debugger.
// I have tried using the DEBUG and RELEASE versions of libcef.so, stripped or non-stripped...
// And nothing seemed to work. Odd.
}
private static string? LocateCefResources()
{
if (ProbeDir(PathHelpers.GetExecutableDirectory(), out var path))
return path;
foreach (var searchDir in NativeDllSearchDirectories())
{
if (ProbeDir(searchDir, out path))
return path;
}
return null;
static bool ProbeDir(string dir, out string path)
{
path = Path.Combine(dir, "cef_resources");
return Directory.Exists(path);
}
}
internal static string[] NativeDllSearchDirectories()
{
var sepChar = OperatingSystem.IsWindows() ? ';' : ':';
var searchDirectories = ((string)AppContext.GetData("NATIVE_DLL_SEARCH_DIRECTORIES")!)
.Split(sepChar, StringSplitOptions.RemoveEmptyEntries);
return searchDirectories;
}
public void Update()
{
// Calling this makes CEF do its work, without using its own update loop.
CefRuntime.DoMessageLoopWork();
}
public void Shutdown()
{
CefRuntime.Shutdown();
}
}
}

View File

@@ -1,127 +0,0 @@
using System;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
namespace Robust.Client.WebView.Headless
{
internal sealed class WebViewManagerHeadless : IWebViewManagerImpl
{
public IWebViewWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams)
{
return new WebViewWindowDummy();
}
public IWebViewControlImpl MakeControlImpl(WebViewControl owner)
{
return new WebViewControlImplDummy();
}
public void Initialize()
{
// Nop
}
public void Update()
{
// Nop
}
public void Shutdown()
{
// Nop
}
private abstract class DummyBase : IWebViewControl
{
public string Url { get; set; } = "about:blank";
public bool IsLoading => true;
public void StopLoad()
{
}
public void Reload()
{
}
public bool GoBack()
{
return false;
}
public bool GoForward()
{
return false;
}
public void ExecuteJavaScript(string code)
{
}
public void AddResourceRequestHandler(Action<IRequestHandlerContext> handler)
{
}
public void RemoveResourceRequestHandler(Action<IRequestHandlerContext> handler)
{
}
}
private sealed class WebViewControlImplDummy : DummyBase, IWebViewControlImpl
{
public void EnteredTree()
{
}
public void ExitedTree()
{
}
public void MouseMove(GUIMouseMoveEventArgs args)
{
}
public void MouseExited()
{
}
public void MouseWheel(GUIMouseWheelEventArgs args)
{
}
public bool RawKeyEvent(in GuiRawKeyEvent guiRawEvent)
{
return false;
}
public void TextEntered(GUITextEventArgs args)
{
}
public void Resized()
{
}
public void Draw(DrawingHandleScreen handle)
{
}
public void AddBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
{
}
public void RemoveBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
{
}
}
private sealed class WebViewWindowDummy : DummyBase, IWebViewWindow
{
public void Dispose()
{
}
public bool Closed => false;
}
}
}

View File

@@ -1,12 +0,0 @@
namespace Robust.Client.WebView
{
public interface IBeforeBrowseContext
{
string Url { get; }
string Method { get; }
bool IsRedirect { get; }
bool UserGesture { get; }
bool IsCancelled { get; }
void DoCancel();
}
}

View File

@@ -1,18 +0,0 @@
using System.IO;
using System.Net;
namespace Robust.Client.WebView
{
public interface IRequestHandlerContext
{
bool IsNavigation { get; }
bool IsDownload { get; }
string RequestInitiator { get; }
string Url { get; }
string Method { get; }
bool IsHandled { get; }
bool IsCancelled { get; }
void DoCancel();
void DoRespondStream(Stream stream, string contentType, HttpStatusCode code = HttpStatusCode.OK);
}
}

View File

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

View File

@@ -1,24 +0,0 @@
using System;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
namespace Robust.Client.WebView
{
/// <summary>
/// Internal swappable implementation of <see cref="WebViewControl"/>.
/// </summary>
internal interface IWebViewControlImpl : IWebViewControl
{
void EnteredTree();
void ExitedTree();
void MouseMove(GUIMouseMoveEventArgs args);
void MouseExited();
void MouseWheel(GUIMouseWheelEventArgs args);
bool RawKeyEvent(in GuiRawKeyEvent guiRawEvent);
void TextEntered(GUITextEventArgs args);
void Resized();
void Draw(DrawingHandleScreen handle);
void AddBeforeBrowseHandler(Action<IBeforeBrowseContext> handler);
void RemoveBeforeBrowseHandler(Action<IBeforeBrowseContext> handler);
}
}

View File

@@ -1,7 +0,0 @@
namespace Robust.Client.WebView
{
public interface IWebViewManager
{
IWebViewWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams);
}
}

View File

@@ -1,14 +0,0 @@
using Robust.Client.WebViewHook;
namespace Robust.Client.WebView
{
/// <summary>
/// Internal implementation of WebViewManager that is switched out by <see cref="IWebViewManagerHook"/>.
/// </summary>
internal interface IWebViewManagerImpl : IWebViewManagerInternal
{
void Initialize();
void Update();
void Shutdown();
}
}

View File

@@ -1,7 +0,0 @@
namespace Robust.Client.WebView
{
internal interface IWebViewManagerInternal : IWebViewManager
{
IWebViewControlImpl MakeControlImpl(WebViewControl owner);
}
}

View File

@@ -1,9 +0,0 @@
using System;
namespace Robust.Client.WebView
{
public interface IWebViewWindow : IWebViewControl, IDisposable
{
bool Closed { get; }
}
}

View File

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

View File

@@ -1,145 +0,0 @@
using System;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.WebView.Cef;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
namespace Robust.Client.WebView
{
/// <summary>
/// An UI control that presents web content.
/// </summary>
public sealed class WebViewControl : Control, IWebViewControl, IRawInputControl
{
[Dependency] private readonly IWebViewManagerInternal _webViewManager = default!;
private readonly IWebViewControlImpl _controlImpl;
[ViewVariables(VVAccess.ReadWrite)]
public string Url
{
get => _controlImpl.Url;
set => _controlImpl.Url = value;
}
[ViewVariables] public bool IsLoading => _controlImpl.IsLoading;
public WebViewControl()
{
CanKeyboardFocus = true;
KeyboardFocusOnClick = true;
MouseFilter = MouseFilterMode.Stop;
IoCManager.InjectDependencies(this);
_controlImpl = _webViewManager.MakeControlImpl(this);
}
protected override void EnteredTree()
{
base.EnteredTree();
_controlImpl.EnteredTree();
}
protected override void ExitedTree()
{
base.ExitedTree();
_controlImpl.ExitedTree();
}
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
{
base.MouseMove(args);
_controlImpl.MouseMove(args);
}
protected internal override void MouseExited()
{
base.MouseExited();
_controlImpl.MouseExited();
}
protected internal override void MouseWheel(GUIMouseWheelEventArgs args)
{
base.MouseWheel(args);
_controlImpl.MouseWheel(args);
}
bool IRawInputControl.RawKeyEvent(in GuiRawKeyEvent guiRawEvent)
{
return _controlImpl.RawKeyEvent(guiRawEvent);
}
protected internal override void TextEntered(GUITextEventArgs args)
{
base.TextEntered(args);
_controlImpl.TextEntered(args);
}
protected override void Resized()
{
base.Resized();
_controlImpl.Resized();
}
protected internal override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
_controlImpl.Draw(handle);
}
public void StopLoad()
{
_controlImpl.StopLoad();
}
public void Reload()
{
_controlImpl.Reload();
}
public bool GoBack()
{
return _controlImpl.GoBack();
}
public bool GoForward()
{
return _controlImpl.GoForward();
}
public void ExecuteJavaScript(string code)
{
_controlImpl.ExecuteJavaScript(code);
}
public void AddResourceRequestHandler(Action<IRequestHandlerContext> handler)
{
_controlImpl.AddResourceRequestHandler(handler);
}
public void RemoveResourceRequestHandler(Action<IRequestHandlerContext> handler)
{
_controlImpl.RemoveResourceRequestHandler(handler);
}
public void AddBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
{
_controlImpl.AddBeforeBrowseHandler(handler);
}
public void RemoveBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
{
_controlImpl.RemoveBeforeBrowseHandler(handler);
}
}
}

View File

@@ -1,59 +0,0 @@
using Robust.Client.WebView;
using Robust.Client.WebView.Cef;
using Robust.Client.WebView.Headless;
using Robust.Client.WebViewHook;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
[assembly: WebViewManagerImpl(typeof(WebViewManager))]
namespace Robust.Client.WebView
{
internal sealed class WebViewManager : IWebViewManagerInternal, IWebViewManagerHook
{
private IWebViewManagerImpl? _impl;
public void Initialize(GameController.DisplayMode mode)
{
DebugTools.Assert(_impl == null, "WebViewManager has already been initialized!");
IoCManager.RegisterInstance<IWebViewManager>(this);
IoCManager.RegisterInstance<IWebViewManagerInternal>(this);
if (mode == GameController.DisplayMode.Headless)
_impl = new WebViewManagerHeadless();
else
_impl = new WebViewManagerCef();
_impl.Initialize();
}
public void Update()
{
DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!");
_impl!.Update();
}
public void Shutdown()
{
DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!");
_impl!.Shutdown();
}
public IWebViewWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams)
{
DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!");
return _impl!.CreateBrowserWindow(createParams);
}
public IWebViewControlImpl MakeControlImpl(WebViewControl owner)
{
DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!");
return _impl!.MakeControlImpl(owner);
}
}
}

View File

@@ -6,8 +6,6 @@ using System.Threading;
using NFluidsynth;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -63,9 +61,8 @@ namespace Robust.Client.Audio.Midi
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
private SharedPhysicsSystem _broadPhaseSystem = default!;
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
[ViewVariables]
public bool IsAvailable
@@ -88,17 +85,16 @@ namespace Robust.Client.Audio.Midi
private float _volume = 0f;
private bool _volumeDirty = true;
// Not reliable until Fluidsynth is initialized!
[ViewVariables(VVAccess.ReadWrite)]
public float Volume
{
get => _volume;
set
{
if (MathHelper.CloseToPercent(_volume, value))
if (MathHelper.CloseTo(_volume, value))
return;
_cfgMan.SetCVar(CVars.MidiVolume, value);
_volume = value;
_volumeDirty = true;
}
}
@@ -135,13 +131,6 @@ namespace Robust.Client.Audio.Midi
{
if (FluidsynthInitialized || _failedInitialize) return;
_volume = _cfgMan.GetCVar(CVars.MidiVolume);
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
{
_volume = value;
_volumeDirty = true;
}, true);
_midiSawmill = Logger.GetSawmill("midi");
_sawmill = Logger.GetSawmill("midi.fluidsynth");
_loggerDelegate = LoggerDelegate;
@@ -177,7 +166,7 @@ namespace Robust.Client.Audio.Midi
_midiThread = new Thread(ThreadUpdate);
_midiThread.Start();
_broadPhaseSystem = EntitySystem.Get<SharedPhysicsSystem>();
_broadPhaseSystem = EntitySystem.Get<SharedBroadPhaseSystem>();
FluidsynthInitialized = true;
}
@@ -227,7 +216,7 @@ namespace Robust.Client.Audio.Midi
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
renderer.LoadSoundfont(FallbackSoundfont);
if (OperatingSystem.IsLinux())
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
foreach (var filepath in LinuxSoundfonts)
{
@@ -245,23 +234,22 @@ namespace Robust.Client.Audio.Midi
break;
}
}
else if (OperatingSystem.IsMacOS())
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
renderer.LoadSoundfont(OsxSoundfont, true);
}
else if (OperatingSystem.IsWindows())
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
renderer.LoadSoundfont(WindowsSoundfont, true);
}
renderer.Source.SetVolume(Volume);
lock (_renderers)
{
_renderers.Add(renderer);
}
return renderer;
}
finally

View File

@@ -1,4 +1,5 @@
using System;
using System.Runtime.Intrinsics.X86;
using NFluidsynth;
using Robust.Client.Graphics;
using Robust.Shared.Asynchronous;
@@ -6,7 +7,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using MidiEvent = NFluidsynth.MidiEvent;
@@ -37,11 +38,6 @@ namespace Robust.Client.Audio.Midi
/// </summary>
bool LoopMidi { get; set; }
/// <summary>
/// This increases all note on velocities to 127.
/// </summary>
bool VolumeBoost { get; set; }
/// <summary>
/// The midi program (instrument) the renderer is using.
/// </summary>
@@ -207,9 +203,7 @@ namespace Robust.Client.Audio.Midi
private const int SampleRate = 44100;
private const int Buffers = SampleRate / 2205;
private readonly object _playerStateLock = new();
private bool _debugEvents = false;
private SequencerClientId _synthRegister;
private SequencerClientId _debugRegister;
public IClydeBufferedAudioSource Source { get; set; }
IClydeBufferedAudioSource IMidiRenderer.Source => Source;
@@ -223,7 +217,7 @@ namespace Robust.Client.Audio.Midi
set
{
lock (_playerStateLock)
for (var i = 0; i < _synth.MidiChannelCount; i++)
for (var i = 0; i < 16; i++)
_synth.ProgramChange(i, value);
_midiProgram = value;
@@ -237,7 +231,7 @@ namespace Robust.Client.Audio.Midi
set
{
lock (_playerStateLock)
for (var i = 0; i < _synth.MidiChannelCount; i++)
for (var i = 0; i < 16; i++)
_synth.BankSelect(i, value);
_midiBank = value;
@@ -251,7 +245,7 @@ namespace Robust.Client.Audio.Midi
set
{
lock (_playerStateLock)
for (var i = 0; i < _synth.MidiChannelCount; i++)
for (var i = 0; i < 16; i++)
_synth.SoundFontSelect(i, value);
_midiSoundfont = value;
@@ -302,9 +296,6 @@ namespace Robust.Client.Audio.Midi
}
}
[ViewVariables(VVAccess.ReadWrite)]
public bool VolumeBoost { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public IEntity? TrackingEntity { get; set; } = null;
@@ -322,7 +313,6 @@ namespace Robust.Client.Audio.Midi
_soundFontLoader = soundFontLoader;
_synth = new Synth(_settings);
_sequencer = new Sequencer(false);
_debugRegister = _sequencer.RegisterClient("honk", DumpSequencerEvent);
_synthRegister = _sequencer.RegisterFluidsynth(_synth);
_synth.AddSoundFontLoader(soundFontLoader);
@@ -332,27 +322,6 @@ namespace Robust.Client.Audio.Midi
Source.StartPlaying();
}
private void DumpSequencerEvent(uint time, SequencerEvent @event)
{
// ReSharper disable once UseStringInterpolation
_midiSawmill.Debug(string.Format(
"{0:D8}: {1} chan:{2:D2} key:{3:D5} bank:{4:D2} ctrl:{5:D5} dur:{6:D5} pitch:{7:D5} prog:{8:D3} val:{9:D5} vel:{10:D5}",
time,
@event.Type.ToString().PadLeft(22),
@event.Channel,
@event.Key,
@event.Bank,
@event.Control,
@event.Duration,
@event.Pitch,
@event.Program,
@event.Value,
@event.Velocity));
@event.Dest = _synthRegister;
_sequencer.SendNow(@event);
}
public bool OpenInput()
{
if (Disposed)
@@ -459,6 +428,9 @@ namespace Robust.Client.Audio.Midi
{
if (Disposed) return;
// SSE needs this.
DebugTools.Assert(length % 4 == 0, "Sample length must be multiple of 4");
var buffersProcessed = Source.GetNumberOfBuffersProcessed();
if(buffersProcessed == Buffers) _midiSawmill.Warning("MIDI buffer overflow!");
if (buffersProcessed == 0) return;
@@ -473,16 +445,36 @@ namespace Robust.Client.Audio.Midi
Source.GetBuffersProcessed(buffers);
lock (_playerStateLock)
{
// _sequencer.Process(10);
_synth?.WriteSampleFloat(length * buffers.Length, audio, 0, Mono ? 1 : 2,
audio, Mono ? length * buffers.Length : 1, Mono ? 1 : 2);
}
if (Mono) // Turn audio to mono
{
var l = length * buffers.Length;
NumericsHelpers.Add(audio[..l], audio[l..]);
if (Sse.IsSupported)
{
fixed (float* ptr = audio)
{
for (var j = 0; j < l; j += 4)
{
var k = j + l;
var jV = Sse.LoadVector128(ptr + j);
var kV = Sse.LoadVector128(ptr + k);
Sse.Store(j + ptr, Sse.Add(jV, kV));
}
}
}
else
{
for (var j = 0; j < l; j++)
{
var k = j + l;
audio[j] = ((audio[k] + audio[j]));
}
}
}
for (var i = 0; i < buffers.Length; i++)
@@ -540,16 +532,14 @@ namespace Robust.Client.Audio.Midi
lock(_playerStateLock)
switch (midiEvent.Type)
{
// Note Off - 0x80
case 128:
_synth.NoteOff(midiEvent.Channel, midiEvent.Key);
// Note On 0x80
case 144:
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
break;
// Note On 0x90
case 144:
if (VolumeBoost)
midiEvent.Velocity = 127;
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
// Note Off - 0x90
case 128:
_synth.NoteOff(midiEvent.Channel, midiEvent.Key);
break;
// After Touch - 0xA
@@ -583,20 +573,10 @@ namespace Robust.Client.Audio.Midi
// Sometimes MIDI files spam these for no good reason and I can't find any info on what they are.
case 1:
case 5:
// MetaEvent -- SetTempo - 0x51
case 81:
// Already handled by the player.
return;
// System Messages - 0xF0
case 240:
switch ((byte)midiEvent.Control)
{
case 11:
_synth.AllNotesOff(midiEvent.Channel);
break;
}
break;
return;
default:
_midiSawmill.Warning("Unhandled midi event of type {0}", midiEvent.Type, midiEvent);
@@ -617,7 +597,7 @@ namespace Robust.Client.Audio.Midi
if (Disposed) return;
var seqEv = (SequencerEvent) midiEvent;
seqEv.Dest = _debugEvents ? _debugRegister : _synthRegister;
seqEv.Dest = _synthRegister;
_sequencer.SendAt(seqEv, time, absolute);
}
@@ -640,15 +620,10 @@ namespace Robust.Client.Audio.Midi
void IMidiRenderer.InternalDispose()
{
Source?.Dispose();
_driver?.Dispose();
// Do NOT dispose of the sequencer after the synth or it'll cause a segfault for some fucking reason.
_sequencer?.UnregisterClient(_debugRegister);
_sequencer?.UnregisterClient(_synthRegister);
_sequencer?.Dispose();
_synth?.Dispose();
_player?.Dispose();
_driver?.Dispose();
_sequencer?.Dispose();
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Net;
using Robust.Client.Debugging;
using Robust.Client.GameObjects;
@@ -31,6 +31,7 @@ namespace Robust.Client
[Dependency] private readonly IDiscordRichPresence _discord = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IClientGameStateManager _gameStates = default!;
[Dependency] private readonly IDebugDrawingManager _debugDrawMan = default!;
/// <inheritdoc />
public ushort DefaultPort { get; } = 1212;
@@ -56,6 +57,7 @@ namespace Robust.Client
_configManager.OnValueChanged(CVars.NetTickrate, TickRateChanged, invokeImmediately: true);
_playMan.Initialize();
_debugDrawMan.Initialize();
Reset();
}

View File

@@ -25,7 +25,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
@@ -47,15 +46,13 @@ namespace Robust.Client
IoCManager.Register<IClientMapManager, ClientMapManager>();
IoCManager.Register<IEntityManager, ClientEntityManager>();
IoCManager.Register<IEntityLookup, EntityLookup>();
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
IoCManager.Register<IComponentFactory, ClientComponentFactory>();
IoCManager.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
IoCManager.Register<IClydeTileDefinitionManager, ClydeTileDefinitionManager>();
IoCManager.Register<GameController, GameController>();
IoCManager.Register<IGameController, GameController>();
IoCManager.Register<IGameControllerInternal, GameController>();
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
IoCManager.Register<IResourceManager, ResourceCache>();
IoCManager.Register<IResourceManagerInternal, ResourceCache>();
IoCManager.Register<IResourceCache, ResourceCache>();
@@ -72,11 +69,13 @@ namespace Robust.Client
IoCManager.Register<IUserInterfaceManager, UserInterfaceManager>();
IoCManager.Register<IUserInterfaceManagerInternal, UserInterfaceManager>();
IoCManager.Register<IDebugDrawing, DebugDrawing>();
IoCManager.Register<IDebugDrawingManager, DebugDrawingManager>();
IoCManager.Register<ILightManager, LightManager>();
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IMidiManager, MidiManager>();
IoCManager.Register<IAuthManager, AuthManager>();
IoCManager.Register<IPhysicsManager, PhysicsManager>();
switch (mode)
{
case GameController.DisplayMode.Headless:

View File

@@ -17,7 +17,6 @@ namespace Robust.Client
public bool Launcher { get; }
public string? Username { get; }
public IReadOnlyCollection<(string key, string value)> CVars { get; }
public IReadOnlyCollection<(string key, string value)> LogLevels { get; }
// Manual parser because C# has no good command line parsing libraries. Also dependencies bad.
// Also I don't like spending 100ms parsing command line args. Do you?
@@ -32,7 +31,6 @@ namespace Robust.Client
var launcher = false;
string? username = null;
var cvars = new List<(string, string)>();
var logLevels = new List<(string, string)>();
var mountOptions = new MountOptions();
using var enumerator = args.GetEnumerator();
@@ -126,26 +124,6 @@ namespace Robust.Client
mountOptions.DirMounts.Add(enumerator.Current);
}
else if (arg == "--loglevel")
{
if (!enumerator.MoveNext())
{
C.WriteLine("Missing loglevel sawmill.");
return false;
}
var loglevel = enumerator.Current;
DebugTools.AssertNotNull(loglevel);
var pos = loglevel.IndexOf('=');
if (pos == -1)
{
C.WriteLine("Expected = in loglevel.");
return false;
}
logLevels.Add((loglevel[..pos], loglevel[(pos + 1)..]));
}
else if (arg == "--help")
{
PrintHelp();
@@ -164,7 +142,6 @@ namespace Robust.Client
launcher,
username,
cvars,
logLevels,
connectAddress,
ss14Address,
mountOptions);
@@ -185,7 +162,6 @@ Options:
--launcher Run in launcher mode (no main menu, auto connect).
--username Override username.
--cvar Specifies an additional cvar overriding the config file. Syntax is <key>=<value>
--loglevel Specifies an additional sawmill log level overriding the default values. Syntax is <key>=<value>
--mount-dir Resource directory to mount.
--mount-zip Resource zip to mount.
--help Display this help text and exit.
@@ -199,7 +175,6 @@ Options:
bool launcher,
string? username,
IReadOnlyCollection<(string key, string value)> cVars,
IReadOnlyCollection<(string key, string value)> logLevels,
string connectAddress, string? ss14Address,
MountOptions mountOptions)
{
@@ -209,7 +184,6 @@ Options:
Launcher = launcher;
Username = username;
CVars = cVars;
LogLevels = logLevels;
ConnectAddress = connectAddress;
Ss14Address = ss14Address;
MountOptions = mountOptions;

View File

@@ -36,7 +36,7 @@ namespace Robust.Client.Console
Message = message;
}
}
/// <inheritdoc cref="IClientConsoleHost" />
internal class ClientConsoleHost : ConsoleHost, IClientConsoleHost
{
@@ -45,15 +45,11 @@ namespace Robust.Client.Console
/// <inheritdoc />
public void Initialize()
{
NetManager.RegisterNetMessage<MsgConCmdReg>(HandleConCmdReg);
NetManager.RegisterNetMessage<MsgConCmdAck>(HandleConCmdAck);
NetManager.RegisterNetMessage<MsgConCmd>(ProcessCommand);
NetManager.RegisterNetMessage<MsgConCmdReg>(MsgConCmdReg.NAME, HandleConCmdReg);
NetManager.RegisterNetMessage<MsgConCmdAck>(MsgConCmdAck.NAME, HandleConCmdAck);
NetManager.RegisterNetMessage<MsgConCmd>(MsgConCmd.NAME, ProcessCommand);
_requestedCommands = false;
NetManager.Connected += OnNetworkConnected;
LoadConsoleCommands();
SendServerCommandRequest();
Reset();
LogManager.RootSawmill.AddHandler(new DebugConsoleLogHandler(this));
}
@@ -65,6 +61,17 @@ namespace Robust.Client.Console
ExecuteCommand(null, text);
}
/// <inheritdoc />
public void Reset()
{
AvailableCommands.Clear();
_requestedCommands = false;
NetManager.Connected += OnNetworkConnected;
LoadConsoleCommands();
SendServerCommandRequest();
}
/// <inheritdoc />
public event EventHandler<AddStringArgs>? AddString;
@@ -83,8 +90,6 @@ namespace Robust.Client.Console
OutputText(text, true, true);
}
public override event ConAnyCommandCallback? AnyCommandExecuted;
/// <inheritdoc />
public override void ExecuteCommand(ICommonSession? session, string command)
{
@@ -92,7 +97,7 @@ namespace Robust.Client.Console
return;
// echo the command locally
WriteLine(null, "> " + command);
WriteError(null, "> " + command);
//Commands are processed locally and then sent to the server to be processed there again.
var args = new List<string>();
@@ -105,11 +110,7 @@ namespace Robust.Client.Console
{
var command1 = AvailableCommands[commandName];
args.RemoveAt(0);
var shell = new ConsoleShell(this, null);
var cmdArgs = args.ToArray();
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
command1.Execute(shell, command, cmdArgs);
command1.Execute(new ConsoleShell(this, null), command, args.ToArray());
}
else
WriteError(null, "Unknown command: " + commandName);
@@ -141,9 +142,6 @@ namespace Robust.Client.Console
private void OutputText(string text, bool local, bool error)
{
AddString?.Invoke(this, new AddStringArgs(text, local, error));
var level = error ? LogLevel.Warning : LogLevel.Info;
Logger.LogS(level, "CON", text);
}
private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs)

View File

@@ -24,6 +24,7 @@ namespace Robust.Client.Console.Commands
var entityUid = EntityUid.Parse(args[0]);
var componentName = args[1];
var compManager = IoCManager.Resolve<IComponentManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
var entityManager = IoCManager.Resolve<IEntityManager>();
@@ -32,7 +33,7 @@ namespace Robust.Client.Console.Commands
component.Owner = entity;
entityManager.AddComponent(entity, component);
compManager.AddComponent(entity, component);
}
}
@@ -54,12 +55,12 @@ namespace Robust.Client.Console.Commands
var entityUid = EntityUid.Parse(args[0]);
var componentName = args[1];
var entManager = IoCManager.Resolve<IEntityManager>();
var compManager = IoCManager.Resolve<IComponentManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
var registration = compFactory.GetRegistration(componentName);
entManager.RemoveComponent(entityUid, registration.Type);
compManager.RemoveComponent(entityUid, registration.Type);
}
}
}

View File

@@ -27,7 +27,6 @@ using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.Console.Commands
{
@@ -78,7 +77,7 @@ namespace Robust.Client.Console.Commands
message.Append($"net ID: {registration.NetID}");
}
message.Append($", References:");
message.Append($", NSE: {registration.NetworkSynchronizeExistence}, references:");
shell.WriteLine(message.ToString());
@@ -161,6 +160,19 @@ namespace Robust.Client.Console.Commands
}
}
internal class ShowBoundingBoxesCommand : IConsoleCommand
{
public string Command => "showbb";
public string Help => "";
public string Description => "Enables debug drawing over all bounding boxes in the game, showing their size.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = IoCManager.Resolve<IDebugDrawing>();
mgr.DebugColliders = !mgr.DebugColliders;
}
}
internal class ShowPositionsCommand : IConsoleCommand
{
public string Command => "showpos";
@@ -188,16 +200,16 @@ namespace Robust.Client.Console.Commands
return;
}
if (!float.TryParse(args[0], out var duration))
if (!int.TryParse(args[0], out var id))
{
shell.WriteError($"{args[0]} is not a valid float.");
shell.WriteError($"{args[0]} is not a valid integer.");
return;
}
var mgr = EntitySystem.Get<DebugRayDrawingSystem>();
var mgr = IoCManager.Resolve<IDebugDrawingManager>();
mgr.DebugDrawRays = !mgr.DebugDrawRays;
shell.WriteError("Toggled showing rays to:" + mgr.DebugDrawRays);
mgr.DebugRayLifetime = TimeSpan.FromSeconds(duration);
shell.WriteError("Toggled showing rays to:" + mgr.DebugDrawRays.ToString());
mgr.DebugRayLifetime = TimeSpan.FromSeconds((double)int.Parse(args[0], CultureInfo.InvariantCulture));
}
}
@@ -447,18 +459,13 @@ namespace Robust.Client.Console.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var uiMgr = IoCManager.Resolve<IUserInterfaceManager>();
var root = IoCManager.Resolve<IUserInterfaceManager>().RootControl;
var res = IoCManager.Resolve<IResourceManager>();
using (var stream = res.UserData.Create(new ResourcePath("/guidump.txt")))
using (var writer = new StreamWriter(stream, EncodingHelpers.UTF8))
{
foreach (var root in uiMgr.AllRoots)
{
writer.WriteLine($"ROOT: {root}");
_writeNode(root, 0, writer);
writer.WriteLine("---------------");
}
_writeNode(root, 0, writer);
}
shell.WriteLine("Saved guidump");
@@ -468,7 +475,7 @@ namespace Robust.Client.Console.Commands
{
var indentation = new string(' ', indents * 2);
writer.WriteLine("{0}{1}", indentation, control);
foreach (var (key, value) in PropertyValuesFor(control))
foreach (var (key, value) in _propertyValuesFor(control))
{
writer.WriteLine("{2} * {0}: {1}", key, value, indentation);
}
@@ -479,14 +486,14 @@ namespace Robust.Client.Console.Commands
}
}
internal static List<(string, string)> PropertyValuesFor(Control control)
private static List<(string, string)> _propertyValuesFor(Control control)
{
var members = new List<(string, string)>();
var type = control.GetType();
foreach (var fieldInfo in type.GetAllFields())
{
if (!ViewVariablesUtility.TryGetViewVariablesAccess(fieldInfo, out _))
if (fieldInfo.GetCustomAttribute<ViewVariablesAttribute>() == null)
{
continue;
}
@@ -496,7 +503,7 @@ namespace Robust.Client.Console.Commands
foreach (var propertyInfo in type.GetAllProperties())
{
if (!ViewVariablesUtility.TryGetViewVariablesAccess(propertyInfo, out _))
if (propertyInfo.GetCustomAttribute<ViewVariablesAttribute>() == null)
{
continue;
}
@@ -529,10 +536,7 @@ namespace Robust.Client.Console.Commands
var scroll = new ScrollContainer();
tabContainer.AddChild(scroll);
//scroll.SetAnchorAndMarginPreset(Control.LayoutPreset.Wide);
var vBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
var vBox = new VBoxContainer();
scroll.AddChild(vBox);
var progressBar = new ProgressBar { MaxValue = 10, Value = 5 };
@@ -590,10 +594,7 @@ namespace Robust.Client.Console.Commands
}
var group = new ButtonGroup();
var vBoxRadioButtons = new BoxContainer
{
Orientation = LayoutOrientation.Vertical
};
var vBoxRadioButtons = new VBoxContainer();
for (var i = 0; i < 10; i++)
{
vBoxRadioButtons.AddChild(new Button
@@ -609,9 +610,8 @@ namespace Robust.Client.Console.Commands
TabContainer.SetTabTitle(vBoxRadioButtons, "Radio buttons!!");
tabContainer.AddChild(new BoxContainer
tabContainer.AddChild(new VBoxContainer
{
Orientation = LayoutOrientation.Vertical,
Name = "Slider",
Children =
{
@@ -619,9 +619,8 @@ namespace Robust.Client.Console.Commands
}
});
tabContainer.AddChild(new SplitContainer
tabContainer.AddChild(new HSplitContainer
{
Orientation = SplitContainer.SplitOrientation.Horizontal,
Children =
{
new PanelContainer
@@ -864,7 +863,7 @@ namespace Robust.Client.Console.Commands
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
var chunk = internalGrid.GetChunk(chunkIndex);
shell.WriteLine($"worldBounds: {chunk.CalcWorldAABB()} localBounds: {chunk.CalcLocalBounds()}");
shell.WriteLine($"worldBounds: {chunk.CalcWorldBounds()} localBounds: {chunk.CalcLocalBounds()}");
}
}

View File

@@ -1,19 +0,0 @@
#if DEBUG
using Robust.Client.Debugging;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
namespace Robust.Client.Console.Commands
{
public sealed class DebugAnchoredCommand : IConsoleCommand
{
public string Command => "showanchored";
public string Description => $"Shows anchored entities on a particular tile";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugAnchoringSystem>().Enabled ^= true;
}
}
}
#endif

View File

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

View File

@@ -63,7 +63,7 @@ namespace Robust.Client.Console.Commands
var conGroup = IoCManager.Resolve<IClientConGroupController>();
foreach (var command in shell.ConsoleHost.RegisteredCommands.Values
.Where(p => p.Command.Contains(filter) && (p is not ServerDummyCommand || conGroup.CanCommand(p.Command)))
.Where(p => p.Command.Contains(filter) && conGroup.CanCommand(p.Command))
.OrderBy(c => c.Command))
{
shell.WriteLine(command.Command + ": " + command.Description);

View File

@@ -1,19 +0,0 @@
#if DEBUG
using Robust.Client.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
namespace Robust.Client.Console.Commands
{
internal sealed class LightDebugCommand : IConsoleCommand
{
public string Command => "lightbb";
public string Description => "Toggles whether to show light bounding boxes";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<DebugLightTreeSystem>().Enabled ^= true;
}
}
}
#endif

View File

@@ -7,8 +7,8 @@ namespace Robust.Client.Console.Commands
public sealed class PhysicsOverlayCommands : IConsoleCommand
{
public string Command => "physics";
public string Description => $"Shows a debug physics overlay. The arg supplied specifies the overlay.";
public string Help => $"{Command} <aabbs / contactnormals / contactpoints / joints / shapeinfo / shapes>";
public string Description => $"{Command} <contactnormals / contactpoints / shapes>";
public string Help => $"{Command} <overlay>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
@@ -21,21 +21,12 @@ namespace Robust.Client.Console.Commands
switch (args[0])
{
case "aabbs":
system.Flags ^= PhysicsDebugFlags.AABBs;
break;
case "contactnormals":
system.Flags ^= PhysicsDebugFlags.ContactNormals;
break;
case "contactpoints":
system.Flags ^= PhysicsDebugFlags.ContactPoints;
break;
case "joints":
system.Flags ^= PhysicsDebugFlags.Joints;
break;
case "shapeinfo":
system.Flags ^= PhysicsDebugFlags.ShapeInfo;
break;
case "shapes":
system.Flags ^= PhysicsDebugFlags.Shapes;
break;

View File

@@ -1,5 +1,6 @@
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Robust.Client.Console.Commands
{
@@ -40,7 +41,7 @@ namespace Robust.Client.Console.Commands
var mgr = IoCManager.Resolve<IScriptClient>();
if (!mgr.CanScript)
{
shell.WriteError("You do not have server side scripting permission.");
shell.WriteError(Loc.GetString("You do not have server side scripting permission."));
return;
}

View File

@@ -1,127 +0,0 @@
using System;
using System.Collections.Immutable;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Input;
using Robust.Shared.Maths;
using Robust.Shared.Network.Messages;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BoxContainer;
using static Robust.Shared.Network.Messages.MsgScriptCompletionResponse;
namespace Robust.Client.Console
{
public class Completions : SS14Window
{
private HistoryLineEdit _textBar;
private ScrollContainer _suggestPanel = new()
{
HScrollEnabled = false,
};
private BoxContainer _suggestBox = new()
{
Orientation = LayoutOrientation.Vertical,
HorizontalAlignment = HAlignment.Left,
};
public Completions(HistoryLineEdit textBar) : base()
{
Title = "Suggestions";
MouseFilter = MouseFilterMode.Pass;
_textBar = textBar;
_suggestPanel.AddChild(_suggestBox);
ContentsContainer.AddChild(_suggestPanel);
}
private bool _firstopen = true;
public void OpenAt(Vector2 position, Vector2 size)
{
if (_firstopen)
{
SetSize = size;
LayoutContainer.SetPosition(this, position);
_firstopen = false;
}
Open();
}
private ImmutableArray<LiteResult> _results;
public void Update()
{
_suggestBox.RemoveAllChildren();
foreach (var res in _results)
{
var label = new Entry(res);
label.OnKeyBindDown += ev =>
{
if (ev.Function == EngineKeyFunctions.UIClick)
_textBar.InsertAtCursor(label.Result.Properties["InsertionText"]);
};
_suggestBox.AddChild(label);
}
}
public void TextChanged() => Close();
public void SetSuggestions(MsgScriptCompletionResponse response)
{
_results = response.Results;
Update();
}
// Label and ghetto button.
public class Entry : RichTextLabel
{
public readonly LiteResult Result;
public Entry(LiteResult result)
{
MouseFilter = MouseFilterMode.Stop;
Result = result;
var compl = new FormattedMessage();
var dim = Color.FromHsl((0f, 0f, 0.8f, 1f));
// warning: ew ahead
string basen = "default";
if (Result.Tags.Contains("Interface"))
basen = "interface name";
else if (Result.Tags.Contains("Class"))
basen = "class name";
else if (Result.Tags.Contains("Struct"))
basen = "struct name";
else if (Result.Tags.Contains("Keyword"))
basen = "keyword";
else if (Result.Tags.Contains("Namespace"))
basen = "namespace name";
else if (Result.Tags.Contains("Method"))
basen = "method name";
else if (Result.Tags.Contains("Property"))
basen = "property name";
else if (Result.Tags.Contains("Field"))
basen = "field name";
Color basec = ScriptingColorScheme.ColorScheme[basen];
compl.PushColor(basec * dim);
compl.AddText(Result.DisplayTextPrefix);
compl.PushColor(basec);
compl.AddText(Result.DisplayText);
compl.PushColor(basec * dim);
compl.AddText(Result.DisplayTextSuffix);
compl.AddText(" [" + String.Join(", ", Result.Tags) + "]");
if (Result.InlineDescription.Length != 0)
{
compl.PushNewline();
compl.AddText(": ");
compl.PushColor(Color.LightSlateGray);
compl.AddText(Result.InlineDescription);
}
SetMessage(compl);
}
}
}
}

View File

@@ -11,6 +11,11 @@ namespace Robust.Client.Console
/// </summary>
void Initialize();
/// <summary>
/// Resets the console to a post-initialized state.
/// </summary>
void Reset();
event EventHandler<AddStringArgs> AddString;
event EventHandler<AddFormattedMessageArgs> AddFormatted;

View File

@@ -46,16 +46,6 @@ namespace Robust.Client.Console
}
protected override void Complete()
{
var msg = _client._netManager.CreateNetMessage<MsgScriptCompletion>();
msg.ScriptSession = _session;
msg.Code = InputBar.Text;
msg.Cursor = InputBar.CursorPosition;
_client._netManager.ClientSendMessage(msg);
}
public override void Close()
{
base.Close();
@@ -105,12 +95,6 @@ namespace Robust.Client.Console
OutputPanel.AddText(">");
}
public void ReceiveCompletionResponse(MsgScriptCompletionResponse response)
{
Suggestions.SetSuggestions(response);
Suggestions.OpenAt((Position.X + Size.X, Position.Y), (Size.X / 2, Size.Y));
}
}
}
}

View File

@@ -17,13 +17,11 @@ namespace Robust.Client.Console
public void Initialize()
{
_netManager.RegisterNetMessage<MsgScriptStop>();
_netManager.RegisterNetMessage<MsgScriptEval>();
_netManager.RegisterNetMessage<MsgScriptStart>();
_netManager.RegisterNetMessage<MsgScriptCompletion>();
_netManager.RegisterNetMessage<MsgScriptCompletionResponse>(ReceiveScriptCompletionResponse);
_netManager.RegisterNetMessage<MsgScriptResponse>(ReceiveScriptResponse);
_netManager.RegisterNetMessage<MsgScriptStartAck>(ReceiveScriptStartAckResponse);
_netManager.RegisterNetMessage<MsgScriptStop>(MsgScriptStop.NAME);
_netManager.RegisterNetMessage<MsgScriptEval>(MsgScriptEval.NAME);
_netManager.RegisterNetMessage<MsgScriptStart>(MsgScriptStart.NAME);
_netManager.RegisterNetMessage<MsgScriptResponse>(MsgScriptResponse.NAME, ReceiveScriptResponse);
_netManager.RegisterNetMessage<MsgScriptStartAck>(MsgScriptStartAck.NAME, ReceiveScriptStartAckResponse);
}
private void ReceiveScriptStartAckResponse(MsgScriptStartAck message)
@@ -46,16 +44,6 @@ namespace Robust.Client.Console
console.ReceiveResponse(message);
}
private void ReceiveScriptCompletionResponse(MsgScriptCompletionResponse message)
{
if (!_activeConsoles.TryGetValue(message.ScriptSession, out var console))
{
return;
}
console.ReceiveCompletionResponse(message);
}
public bool CanScript => _conGroupController.CanScript();
public void StartSession()

View File

@@ -1,4 +1,5 @@
#if CLIENT_SCRIPTING
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -13,11 +14,11 @@ using Microsoft.CodeAnalysis.Text;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.ViewVariables;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
using Robust.Shared.Scripting;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using Color = Robust.Shared.Maths.Color;
#nullable enable
@@ -42,20 +43,17 @@ namespace Robust.Client.Console
public ScriptConsoleClient()
{
Title = "Robust C# Interactive (CLIENT)";
Title = Loc.GetString("Robust C# Interactive (CLIENT)");
ScriptInstanceShared.InitDummy();
_globals = new ScriptGlobalsImpl(this);
IoCManager.InjectDependencies(this);
OutputPanel.AddText("Robust C# interactive console (CLIENT).");
OutputPanel.AddText(Loc.GetString(@"Robust C# interactive console (CLIENT)."));
OutputPanel.AddText(">");
}
// No-op for now.
protected override void Complete() { }
protected override async void Run()
{
var code = InputBar.Text;
@@ -120,7 +118,7 @@ namespace Robust.Client.Console
}
else
{
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager).AddReferences(typeof(Image).Assembly);
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager);
newScript = CSharpScript.Create(code, options, typeof(ScriptGlobals));
}

View File

@@ -8,10 +8,11 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
using Robust.Shared.Scripting;
using Robust.Shared.Timing;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.Console
{
@@ -19,7 +20,7 @@ namespace Robust.Client.Console
{
private readonly IReflectionManager _reflectionManager;
private readonly BoxContainer _watchesVBox;
private readonly VBoxContainer _watchesVBox;
private readonly LineEdit _addWatchEdit;
private readonly Button _addWatchButton;
@@ -30,32 +31,29 @@ namespace Robust.Client.Console
ScriptInstanceShared.InitDummy();
Title = "Watch Window";
Title = Loc.GetString("Watch Window");
var mainVBox = new BoxContainer
var mainVBox = new VBoxContainer
{
Orientation = LayoutOrientation.Vertical,
MinSize = (500, 300),
Children =
{
(_watchesVBox = new BoxContainer
(_watchesVBox = new VBoxContainer
{
Orientation = LayoutOrientation.Vertical,
VerticalExpand = true
}),
new BoxContainer
new HBoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
(_addWatchEdit = new HistoryLineEdit
{
HorizontalExpand = true,
PlaceHolder = "Add watch (C# interactive)"
PlaceHolder = Loc.GetString("Add watch (C# interactive)")
}),
(_addWatchButton = new Button
{
Text = "Add"
Text = Loc.GetString("Add")
})
}
}
@@ -109,9 +107,8 @@ namespace Robust.Client.Console
Button delButton;
_runner = runner;
AddChild(new BoxContainer
AddChild(new HBoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
(_outputLabel = new Label
@@ -121,7 +118,7 @@ namespace Robust.Client.Console
}),
(delButton = new Button
{
Text = "Remove"
Text = Loc.GetString("Remove")
}),
}
});
@@ -171,9 +168,8 @@ namespace Robust.Client.Console
public CompilationErrorControl(string message)
{
Button delButton;
AddChild(new BoxContainer
AddChild(new HBoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
new Label
@@ -182,7 +178,7 @@ namespace Robust.Client.Console
ClipText = true,
HorizontalExpand = true
},
(delButton = new Button {Text = "Remove"})
(delButton = new Button {Text = Loc.GetString("Remove")})
}
});

View File

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

View File

@@ -1,96 +0,0 @@
#if DEBUG
using System.Text;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Client.Debugging
{
public sealed class DebugAnchoringSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
private Label? _label;
private (GridId GridId, TileRef Tile)? _hovered;
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
if (_enabled)
{
_label = new Label();
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(_label);
}
else
{
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.RemoveChild(_label!);
_label = null;
_hovered = null;
}
}
}
private bool _enabled;
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
if (!Enabled) return;
if (_label == null)
{
DebugTools.Assert($"Debug Label for {nameof(DebugAnchoringSystem)} is null!");
return;
}
var mouseSpot = _inputManager.MouseScreenPosition;
var spot = _eyeManager.ScreenToMap(mouseSpot);
if (!_mapManager.TryFindGridAt(spot, out var grid))
{
_label.Text = string.Empty;
_hovered = null;
return;
}
var tile = grid.GetTileRef(spot);
_label.Position = mouseSpot.Position + new Vector2(32, 0);
if (_hovered?.GridId == grid.Index && _hovered?.Tile == tile) return;
_hovered = (grid.Index, tile);
var text = new StringBuilder();
foreach (var ent in grid.GetAnchoredEntities(spot))
{
if (!EntityManager.TryGetEntity(ent, out var entity))
{
text.AppendLine($"uid: {ent}, invalid");
}
else
{
text.AppendLine($"uid: {ent}, {entity.Name}");
}
}
_label.Text = text.ToString();
}
}
}
#endif

View File

@@ -1,8 +1,19 @@
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Physics.Dynamics.Joints;
using Robust.Shared.Prototypes;
namespace Robust.Client.Debugging
{
@@ -11,10 +22,39 @@ namespace Robust.Client.Debugging
{
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
private bool _debugColliders;
private bool _debugPositions;
/// <inheritdoc />
public bool DebugColliders
{
get => _debugColliders;
set
{
if (value == DebugColliders)
{
return;
}
_debugColliders = value;
if (value && !_overlayManager.HasOverlay<PhysicsOverlay>())
{
_overlayManager.AddOverlay(new PhysicsOverlay(_eyeManager,
_prototypeManager, _inputManager, _mapManager));
}
else
{
_overlayManager.RemoveOverlay<PhysicsOverlay>();
}
}
}
/// <inheritdoc />
public bool DebugPositions
{
@@ -30,7 +70,7 @@ namespace Robust.Client.Debugging
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
{
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, _eyeManager));
_overlayManager.AddOverlay(new EntityPositionOverlay(_entityManager, _eyeManager));
}
else
{
@@ -39,16 +79,205 @@ namespace Robust.Client.Debugging
}
}
private class PhysicsOverlay : Overlay
{
private readonly IEyeManager _eyeManager;
private readonly IMapManager _mapManager;
private readonly IInputManager _inputManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
private readonly ShaderInstance _shader;
private readonly Font _font;
private Vector2 _hoverStartScreen = Vector2.Zero;
private List<IPhysBody> _hoverBodies = new();
public PhysicsOverlay(IEyeManager eyeMan, IPrototypeManager protoMan, IInputManager inputManager, IMapManager mapManager)
{
_eyeManager = eyeMan;
_inputManager = inputManager;
_mapManager = mapManager;
_shader = protoMan.Index<ShaderPrototype>("unshaded").Instance();
var cache = IoCManager.Resolve<IResourceCache>();
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
}
/// <inheritdoc />
protected internal override void Draw(in OverlayDrawArgs args)
{
switch (args.Space)
{
case OverlaySpace.ScreenSpace:
DrawScreen(args);
break;
case OverlaySpace.WorldSpace:
DrawWorld(args);
break;
}
}
private void DrawScreen(in OverlayDrawArgs args)
{
var screenHandle = args.ScreenHandle;
var lineHeight = _font.GetLineHeight(1f);
Vector2 drawPos = _hoverStartScreen + new Vector2(20, 0) + new Vector2(0, -(_hoverBodies.Count * 4 * lineHeight / 2f));
int row = 0;
foreach (var body in _hoverBodies)
{
if (body != _hoverBodies[0])
{
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), "------");
row++;
}
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
row++;
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
row++;
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
row++;
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
row++;
}
}
private void DrawWorld(in OverlayDrawArgs args)
{
var worldHandle = args.WorldHandle;
worldHandle.UseShader(_shader);
var drawing = new PhysDrawingAdapter(worldHandle);
_hoverBodies.Clear();
var mouseScreenPos = _inputManager.MouseScreenPosition;
var mouseWorldPos = _eyeManager.ScreenToMap(mouseScreenPos).Position;
_hoverStartScreen = mouseScreenPos.Position;
var viewport = _eyeManager.GetWorldViewport();
if (viewport.IsEmpty()) return;
var mapId = _eyeManager.CurrentMap;
var sleepThreshold = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.TimeToSleep);
var colorEdge = Color.Red.WithAlpha(0.33f);
var drawnJoints = new HashSet<Joint>();
foreach (var physBody in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(mapId, viewport))
{
// all entities have a TransformComponent
var transform = physBody.Owner.Transform;
var worldBox = physBody.GetWorldAABB(_mapManager);
if (worldBox.IsEmpty()) continue;
foreach (var fixture in physBody.Fixtures)
{
var shape = fixture.Shape;
var sleepPercent = physBody.Awake ? physBody.SleepTime / sleepThreshold : 1.0f;
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, sleepPercent);
}
foreach (var joint in physBody.Joints)
{
if (drawnJoints.Contains(joint)) continue;
drawnJoints.Add(joint);
joint.DebugDraw(drawing, in viewport);
}
if (worldBox.Contains(mouseWorldPos))
{
_hoverBodies.Add(physBody);
}
// draw AABB
worldHandle.DrawRect(worldBox, colorEdge, false);
}
}
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
{
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
foreach (var rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
baseLine += new Vector2(advance, 0);
}
}
private class PhysDrawingAdapter : DebugDrawingHandle
{
private readonly DrawingHandleWorld _handle;
public PhysDrawingAdapter(DrawingHandleWorld worldHandle)
{
_handle = worldHandle;
}
public override Color WakeMixColor => Color.White;
public override Color GridFillColor => Color.Blue.WithAlpha(0.05f);
public override Color RectFillColor => Color.Green.WithAlpha(0.25f);
public override Color CalcWakeColor(Color color, float wakePercent)
{
var percent = MathHelper.Clamp(wakePercent, 0, 1);
var r = 1 - (percent * (1 - color.R));
var g = 1 - (percent * (1 - color.G));
var b = 1 - (percent * (1 - color.B));
return new Color(r, g, b, color.A);
}
public override void DrawRect(in Box2 box, in Color color)
{
_handle.DrawRect(box, color);
}
public override void DrawRect(in Box2Rotated box, in Color color)
{
_handle.DrawRect(box, color);
}
public override void DrawCircle(Vector2 origin, float radius, in Color color)
{
_handle.DrawCircle(origin, radius, color);
}
public override void DrawPolygonShape(Vector2[] vertices, in Color color)
{
_handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, vertices, color);
}
public override void DrawLine(Vector2 start, Vector2 end, in Color color)
{
_handle.DrawLine(start, end, color);
}
public override void SetTransform(in Matrix3 transform)
{
_handle.SetTransform(transform);
}
}
}
private sealed class EntityPositionOverlay : Overlay
{
private readonly IEntityLookup _lookup;
private readonly IEntityManager _entityManager;
private readonly IEyeManager _eyeManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityPositionOverlay(IEntityLookup lookup, IEyeManager eyeManager)
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager)
{
_lookup = lookup;
_entityManager = entityManager;
_eyeManager = eyeManager;
}
@@ -57,17 +286,18 @@ namespace Robust.Client.Debugging
const float stubLength = 0.25f;
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
var viewport = _eyeManager.GetWorldViewport();
foreach (var entity in _lookup.GetEntitiesIntersecting(_eyeManager.CurrentMap, viewport))
foreach (var entity in _entityManager.GetEntities())
{
var transform = entity.Transform;
if (transform.MapID != _eyeManager.CurrentMap ||
!_eyeManager.GetWorldViewport().Contains(transform.WorldPosition))
{
continue;
}
var center = transform.WorldPosition;
var worldRotation = transform.WorldRotation;
var xLine = worldRotation.RotateVec(Vector2.UnitX);
var yLine = worldRotation.RotateVec(Vector2.UnitY);
var xLine = transform.WorldRotation.RotateVec(Vector2.UnitX);
var yLine = transform.WorldRotation.RotateVec(Vector2.UnitY);
worldHandle.DrawLine(center, center + xLine * stubLength, Color.Red);
worldHandle.DrawLine(center, center + yLine * stubLength, Color.Green);

View File

@@ -1,23 +1,22 @@
using Robust.Shared.IoC;
using Robust.Shared.Network.Messages;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Physics;
using Robust.Shared.Timing;
using Robust.Shared.Enums;
using Robust.Shared.Network;
namespace Robust.Client.Debugging
{
internal sealed class DebugRayDrawingSystem : EntitySystem
internal class DebugDrawingManager : IDebugDrawingManager
{
[Dependency] private readonly IClientNetManager _net = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IGameTiming _gameTimer = default!;
private readonly List<RayWithLifetime> _raysWithLifeTime = new();
private readonly List<RayWithLifetime> raysWithLifeTime = new();
private bool _debugDrawRays;
private struct RayWithLifetime
@@ -53,30 +52,9 @@ namespace Robust.Client.Debugging
public TimeSpan DebugRayLifetime { get; set; } = TimeSpan.FromSeconds(5);
public override void Initialize()
public void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<MsgRay>(HandleDrawRay);
// To catch anything that's client-only and not sent by the server.
SubscribeLocalEvent<DebugDrawRayMessage>(OnDebugDrawRay);
}
private void OnDebugDrawRay(DebugDrawRayMessage ev)
{
if (!_debugDrawRays)
{
return;
}
var newRayWithLifetime = new RayWithLifetime
{
DidActuallyHit = ev.Data.Results != null,
RayOrigin = ev.Data.Ray.Position,
RayHit = ev.Data.Results?.HitPos ?? ev.Data.Ray.Direction * ev.Data.MaxLength + ev.Data.Ray.Position,
LifeTime = _gameTimer.RealTime + DebugRayLifetime
};
_raysWithLifeTime.Add(newRayWithLifetime);
_net.RegisterNetMessage<MsgRay>(MsgRay.NAME, HandleDrawRay);
}
private void HandleDrawRay(MsgRay msg)
@@ -96,15 +74,15 @@ namespace Robust.Client.Debugging
LifeTime = _gameTimer.RealTime + DebugRayLifetime
};
_raysWithLifeTime.Add(newRayWithLifetime);
raysWithLifeTime.Add(newRayWithLifetime);
}
private sealed class DebugDrawRayOverlay : Overlay
{
private readonly DebugRayDrawingSystem _owner;
private readonly DebugDrawingManager _owner;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DebugDrawRayOverlay(DebugRayDrawingSystem owner)
public DebugDrawRayOverlay(DebugDrawingManager owner)
{
_owner = owner;
}
@@ -112,7 +90,7 @@ namespace Robust.Client.Debugging
protected internal override void Draw(in OverlayDrawArgs args)
{
var handle = args.WorldHandle;
foreach (var ray in _owner._raysWithLifeTime)
foreach (var ray in _owner.raysWithLifeTime)
{
handle.DrawLine(
ray.RayOrigin,
@@ -125,7 +103,7 @@ namespace Robust.Client.Debugging
{
base.FrameUpdate(args);
_owner._raysWithLifeTime.RemoveAll(r => r.LifeTime < _owner._gameTimer.RealTime);
_owner.raysWithLifeTime.RemoveAll(r => r.LifeTime < _owner._gameTimer.RealTime);
}
}
}

View File

@@ -20,60 +20,30 @@
* 3. This notice may not be removed or altered from any source distribution.
*/
// MIT License
// Copyright (c) 2019 Erin Catto
// 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.
/* Heavily inspired by Farseer */
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Dynamics.Contacts;
using Robust.Shared.Physics.Dynamics.Joints;
namespace Robust.Client.Debugging
{
public sealed class DebugPhysicsSystem : SharedDebugPhysicsSystem
internal sealed class DebugPhysicsSystem : SharedDebugPhysicsSystem
{
/*
* Used for debugging shapes, controllers, joints, contacts
*/
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
private const int MaxContactPoints = 2048;
internal int PointCount;
internal ContactPoint[] Points = new ContactPoint[MaxContactPoints];
internal ContactPoint[] _points = new ContactPoint[MaxContactPoints];
public PhysicsDebugFlags Flags
{
@@ -83,14 +53,7 @@ namespace Robust.Client.Debugging
if (value == _flags) return;
if (_flags == PhysicsDebugFlags.None)
IoCManager.Resolve<IOverlayManager>().AddOverlay(
new PhysicsDebugOverlay(
EntityManager,
IoCManager.Resolve<IEyeManager>(),
IoCManager.Resolve<IInputManager>(),
IoCManager.Resolve<IResourceCache>(),
this,
Get<SharedPhysicsSystem>()));
IoCManager.Resolve<IOverlayManager>().AddOverlay(new PhysicsDebugOverlay(this));
if (value == PhysicsDebugFlags.None)
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsDebugOverlay));
@@ -103,7 +66,7 @@ namespace Robust.Client.Debugging
public override void HandlePreSolve(Contact contact, in Manifold oldManifold)
{
if ((Flags & (PhysicsDebugFlags.ContactPoints | PhysicsDebugFlags.ContactNormals)) != 0)
if ((Flags & PhysicsDebugFlags.ContactPoints) != 0)
{
Manifold manifold = contact.Manifold;
@@ -112,24 +75,23 @@ namespace Robust.Client.Debugging
Fixture fixtureA = contact.FixtureA!;
var state1 = new PointState[2];
var state2 = new PointState[2];
CollisionManager.GetPointStates(ref state1, ref state2, oldManifold, manifold);
PointState[] state1, state2;
CollisionManager.GetPointStates(out state1, out state2, oldManifold, manifold);
Span<Vector2> points = stackalloc Vector2[2];
contact.GetWorldManifold(_physicsManager, out var normal, points);
Vector2 normal;
contact.GetWorldManifold(out normal, points);
ContactPoint cp = Points[PointCount];
for (var i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
for (int i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
{
if (fixtureA == null)
Points[i] = new ContactPoint();
_points[i] = new ContactPoint();
ContactPoint cp = _points[PointCount];
cp.Position = points[i];
cp.Normal = normal;
cp.State = state2[i];
Points[PointCount] = cp;
_points[PointCount] = cp;
++PointCount;
}
}
@@ -144,288 +106,58 @@ namespace Robust.Client.Debugging
}
[Flags]
public enum PhysicsDebugFlags : byte
internal enum PhysicsDebugFlags : byte
{
None = 0,
/// <summary>
/// Shows the world point for each contact in the viewport.
/// </summary>
ContactPoints = 1 << 0,
/// <summary>
/// Shows the world normal for each contact in the viewport.
/// </summary>
ContactNormals = 1 << 1,
/// <summary>
/// Shows all physics shapes in the viewport.
/// </summary>
Shapes = 1 << 2,
ShapeInfo = 1 << 3,
Joints = 1 << 4,
AABBs = 1 << 5,
}
internal sealed class PhysicsDebugOverlay : Overlay
{
private IEntityManager _entityManager = default!;
private IEyeManager _eyeManager = default!;
private IInputManager _inputManager = default!;
private DebugPhysicsSystem _debugPhysicsSystem = default!;
private SharedPhysicsSystem _physicsSystem = default!;
private DebugPhysicsSystem _physics = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private static readonly Color JointColor = new(0.5f, 0.8f, 0.8f);
private readonly Font _font;
private HashSet<Joint> _drawnJoints = new();
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IResourceCache cache, DebugPhysicsSystem system, SharedPhysicsSystem physicsSystem)
public PhysicsDebugOverlay(DebugPhysicsSystem system)
{
_entityManager = entityManager;
_eyeManager = eyeManager;
_inputManager = inputManager;
_debugPhysicsSystem = system;
_physicsSystem = physicsSystem;
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
}
private void DrawWorld(DrawingHandleWorld worldHandle)
{
var viewport = _eyeManager.GetWorldViewport();
var viewBounds = _eyeManager.GetWorldViewbounds();
var mapId = _eyeManager.CurrentMap;
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Shapes) != 0 && !viewport.IsEmpty())
{
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
{
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
var xform = physBody.GetTransform();
const float AlphaModifier = 0.2f;
foreach (var fixture in physBody.Fixtures)
{
// Invalid shape - Box2D doesn't check for IsSensor
if (physBody.BodyType == BodyType.Dynamic && fixture.Mass == 0f)
{
DrawShape(worldHandle, fixture, xform, Color.Red.WithAlpha(AlphaModifier));
}
else if (!physBody.CanCollide)
{
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.5f, 0.3f).WithAlpha(AlphaModifier));
}
else if (physBody.BodyType == BodyType.Static)
{
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.9f, 0.5f).WithAlpha(AlphaModifier));
}
else if ((physBody.BodyType & (BodyType.Kinematic | BodyType.KinematicController)) != 0x0)
{
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.5f, 0.9f).WithAlpha(AlphaModifier));
}
else if (!physBody.Awake)
{
DrawShape(worldHandle, fixture, xform, new Color(0.6f, 0.6f, 0.6f).WithAlpha(AlphaModifier));
}
else
{
DrawShape(worldHandle, fixture, xform, new Color(0.9f, 0.7f, 0.7f).WithAlpha(AlphaModifier));
}
}
}
}
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.AABBs) != 0 && !viewport.IsEmpty())
{
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
{
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
var xform = physBody.GetTransform();
const float AlphaModifier = 0.2f;
Box2? aabb = null;
foreach (var fixture in physBody.Fixtures)
{
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
var shapeBB = fixture.Shape.ComputeAABB(xform, i);
aabb = aabb?.Union(shapeBB) ?? shapeBB;
}
}
if (aabb == null) continue;
worldHandle.DrawRect(aabb.Value, Color.Red.WithAlpha(AlphaModifier), false);
}
}
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Joints) != 0x0)
{
_drawnJoints.Clear();
foreach (var jointComponent in _entityManager.EntityQuery<JointComponent>(true))
{
if (jointComponent.JointCount == 0 ||
!_entityManager.TryGetComponent(jointComponent.Owner.Uid, out TransformComponent? xf1) ||
!viewport.Contains(xf1.WorldPosition)) continue;
foreach (var (_, joint) in jointComponent.Joints)
{
if (_drawnJoints.Contains(joint)) continue;
DrawJoint(worldHandle, joint);
_drawnJoints.Add(joint);
}
}
}
if ((_debugPhysicsSystem.Flags & (PhysicsDebugFlags.ContactPoints | PhysicsDebugFlags.ContactNormals)) != 0)
{
const float axisScale = 0.3f;
for (var i = 0; i < _debugPhysicsSystem.PointCount; ++i)
{
var point = _debugPhysicsSystem.Points[i];
const float radius = 0.1f;
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ContactPoints) != 0)
{
if (point.State == PointState.Add)
worldHandle.DrawCircle(point.Position, radius, new Color(255, 77, 243, 255));
else if (point.State == PointState.Persist)
worldHandle.DrawCircle(point.Position, radius, new Color(255, 77, 77, 255));
}
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ContactNormals) != 0)
{
Vector2 p1 = point.Position;
Vector2 p2 = p1 + point.Normal * axisScale;
worldHandle.DrawLine(p1, p2, new Color(255, 102, 230, 255));
}
}
_debugPhysicsSystem.PointCount = 0;
}
}
private void DrawScreen(DrawingHandleScreen screenHandle)
{
var mapId = _eyeManager.CurrentMap;
var mousePos = _inputManager.MouseScreenPosition;
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ShapeInfo) != 0x0)
{
var hoverBodies = new List<PhysicsComponent>();
var bounds = Box2.UnitCentered.Translated(_eyeManager.ScreenToMap(mousePos.Position).Position);
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, bounds))
{
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
hoverBodies.Add(physBody);
}
var lineHeight = _font.GetLineHeight(1f);
var drawPos = mousePos.Position + new Vector2(20, 0) + new Vector2(0, -(hoverBodies.Count * 4 * lineHeight / 2f));
int row = 0;
foreach (var body in hoverBodies)
{
if (body != hoverBodies[0])
{
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), "------");
row++;
}
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
row++;
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
row++;
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
row++;
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
row++;
}
}
_physics = system;
}
protected internal override void Draw(in OverlayDrawArgs args)
{
if (_debugPhysicsSystem.Flags == PhysicsDebugFlags.None) return;
if (_physics.Flags == PhysicsDebugFlags.None) return;
switch (args.Space)
var worldHandle = args.WorldHandle;
if ((_physics.Flags & PhysicsDebugFlags.Shapes) != 0)
{
case OverlaySpace.ScreenSpace:
DrawScreen((DrawingHandleScreen) args.DrawingHandle);
break;
case OverlaySpace.WorldSpace:
DrawWorld((DrawingHandleWorld) args.DrawingHandle);
break;
// Port DebugDrawing over.
}
}
private void DrawShape(DrawingHandleWorld worldHandle, Fixture fixture, Transform xform, Color color)
{
switch (fixture.Shape)
if ((_physics.Flags & PhysicsDebugFlags.ContactPoints) != 0)
{
case PhysShapeCircle circle:
var center = Transform.Mul(xform, circle.Position);
worldHandle.DrawCircle(center, circle.Radius, color);
break;
case EdgeShape edge:
var v1 = Transform.Mul(xform, edge.Vertex1);
var v2 = Transform.Mul(xform, edge.Vertex2);
worldHandle.DrawLine(v1, v2, color);
const float axisScale = 0.3f;
if (edge.OneSided)
for (int i = 0; i < _physics.PointCount; ++i)
{
DebugPhysicsSystem.ContactPoint point = _physics._points[i];
if (point.State == PointState.Add)
worldHandle.DrawCircle(point.Position, 0.5f, new Color(255, 77, 243, 77));
else if (point.State == PointState.Persist)
worldHandle.DrawCircle(point.Position, 0.5f, new Color(255, 77, 77, 77));
if ((_physics.Flags & PhysicsDebugFlags.ContactNormals) != 0)
{
worldHandle.DrawCircle(v1, 0.5f, color);
worldHandle.DrawCircle(v2, 0.5f, color);
Vector2 p1 = point.Position;
Vector2 p2 = p1 + point.Normal * axisScale;
worldHandle.DrawLine(p1, p2, new Color(255, 102, 230, 102));
}
}
break;
case PolygonShape poly:
Span<Vector2> verts = stackalloc Vector2[poly.Vertices.Length];
for (var i = 0; i < verts.Length; i++)
{
verts[i] = Transform.Mul(xform, poly.Vertices[i]);
}
worldHandle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, color);
break;
default:
return;
}
}
private void DrawJoint(DrawingHandleWorld worldHandle, Joint joint)
{
if (!_entityManager.TryGetComponent(joint.BodyAUid, out TransformComponent? xform1) ||
!_entityManager.TryGetComponent(joint.BodyBUid, out TransformComponent? xform2)) return;
var matrix1 = xform1.WorldMatrix;
var matrix2 = xform2.WorldMatrix;
var xf1 = new Vector2(matrix1.R0C2, matrix1.R1C2);
var xf2 = new Vector2(matrix2.R0C2, matrix2.R1C2);
var p1 = matrix1.Transform(joint.LocalAnchorA);
var p2 = matrix2.Transform(joint.LocalAnchorB);
switch (joint)
{
case DistanceJoint:
worldHandle.DrawLine(xf1, xf2, JointColor);
break;
default:
worldHandle.DrawLine(xf1, p1, JointColor);
worldHandle.DrawLine(p1, p2, JointColor);
worldHandle.DrawLine(xf2, p2, JointColor);
break;
_physics.PointCount = 0;
}
}
}

View File

@@ -5,6 +5,11 @@
/// </summary>
public interface IDebugDrawing
{
/// <summary>
/// Toggles the visual overlay of physics bodies for each entity on screen.
/// </summary>
bool DebugColliders { get; set; }
/// <summary>
/// Toggles the visual overlay of the local origin for each entity on screen.
/// </summary>

View File

@@ -0,0 +1,11 @@
using System;
namespace Robust.Client.Debugging
{
public interface IDebugDrawingManager
{
bool DebugDrawRays { get; set; }
TimeSpan DebugRayLifetime { get; set; }
void Initialize();
}
}

View File

@@ -11,7 +11,7 @@ namespace Robust.Client
{
public void Main(IMainArgs args)
{
Start(args.Args, new GameControllerOptions(), contentStart: false, args);
Start(args.Args, contentStart: false, args);
}
}
}

View File

@@ -1,52 +0,0 @@
using System;
using System.Reflection;
using Robust.Client.WebViewHook;
using Robust.Shared.Log;
using Robust.Shared.Utility;
namespace Robust.Client
{
internal sealed partial class GameController
{
private void LoadOptionalRobustModules(GameController.DisplayMode mode)
{
// In the future, this manifest should be loaded somewhere else and used for more parts of init.
// For now, this is fine.
var manifest = LoadResourceManifest();
foreach (var module in manifest.Modules)
{
switch (module)
{
case "Robust.Client.WebView":
LoadRobustWebView(mode);
break;
default:
Logger.Error($"Unknown Robust module: {module}");
return;
}
}
}
private void LoadRobustWebView(GameController.DisplayMode mode)
{
Logger.Debug("Loading Robust.Client.WebView");
var assembly = LoadRobustModuleAssembly("Robust.Client.WebView");
var attribute = assembly.GetCustomAttribute<WebViewManagerImplAttribute>()!;
DebugTools.AssertNotNull(attribute);
var managerType = attribute.ImplementationType;
_webViewHook = (IWebViewManagerHook)Activator.CreateInstance(managerType)!;
_webViewHook.Initialize(mode);
Logger.Debug("Done initializing Robust.Client.WebView");
}
private Assembly LoadRobustModuleAssembly(string assemblyName)
{
// TODO: Launcher distribution and all that stuff.
return Assembly.Load(assemblyName);
}
}
}

View File

@@ -22,10 +22,10 @@ namespace Robust.Client
public static void Main(string[] args)
{
Start(args, new GameControllerOptions());
Start(args);
}
public static void Start(string[] args, GameControllerOptions options, bool contentStart = false, IMainArgs? loaderArgs=null)
public static void Start(string[] args, bool contentStart = false, IMainArgs? loaderArgs=null, GameControllerOptions? options = null)
{
if (_hasStarted)
{
@@ -40,7 +40,7 @@ namespace Robust.Client
}
}
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions options)
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs, GameControllerOptions? options)
{
IoCManager.InitThread();
@@ -51,13 +51,15 @@ namespace Robust.Client
var gc = IoCManager.Resolve<GameController>();
gc.SetCommandLineArgs(args);
gc._loaderArgs = loaderArgs;
if(options != null)
gc.Options = options;
// When the game is ran with the startup executable being content,
// we have to disable the separate load context.
// Otherwise the content assemblies will be loaded twice which causes *many* fun bugs.
gc.ContentStart = contentStart;
gc.Run(mode, options);
gc.Run(mode);
}
public void OverrideMainLoop(IGameLoop gameLoop)
@@ -65,9 +67,9 @@ namespace Robust.Client
_mainLoop = gameLoop;
}
public void Run(DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null)
public void Run(DisplayMode mode, Func<ILogHandler>? logHandlerFactory = null)
{
if (!StartupSystemSplash(options, logHandlerFactory))
if (!StartupSystemSplash(logHandlerFactory))
{
Logger.Fatal("Failed to start game controller!");
return;

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