mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ca686298e | ||
|
|
5e914cb13a | ||
|
|
a1bdfca8ba | ||
|
|
79deaca409 | ||
|
|
2eeb21431b | ||
|
|
c4062bcae9 | ||
|
|
cd3a85ea04 | ||
|
|
d15b5c7f22 | ||
|
|
18bbe2271d | ||
|
|
ee2b7a3a66 | ||
|
|
ca36671131 | ||
|
|
604a1a6960 | ||
|
|
2898f5396f | ||
|
|
39541639c5 | ||
|
|
50981ad1a1 | ||
|
|
0cbfbeffae | ||
|
|
e603153016 | ||
|
|
e7c417ca0c | ||
|
|
a3989f28eb | ||
|
|
38ace3c348 | ||
|
|
0e00170f45 | ||
|
|
261ee96cad | ||
|
|
2c851885db | ||
|
|
849be86455 | ||
|
|
ffd5c120be | ||
|
|
76b15dda70 | ||
|
|
0bf7f519ad | ||
|
|
f97f325a36 | ||
|
|
24315fa787 | ||
|
|
792179657b | ||
|
|
9b92bcf911 | ||
|
|
86e34ea27b | ||
|
|
62cf778958 | ||
|
|
5d667e44c3 | ||
|
|
6d84b8741c | ||
|
|
d08ca59b75 | ||
|
|
f06b046c1c | ||
|
|
bb412a6906 | ||
|
|
1baee3004c | ||
|
|
f18068c13a | ||
|
|
0ba00a1845 | ||
|
|
28caf0d74c | ||
|
|
ecbb32b70b | ||
|
|
8e2a9cc597 | ||
|
|
a7eb8201c9 | ||
|
|
1f95fe6782 | ||
|
|
07c1f9e1af | ||
|
|
826dce6659 | ||
|
|
cdf714f3ba | ||
|
|
671ca7959c | ||
|
|
b7a1345d3a | ||
|
|
835b6ebdba | ||
|
|
0ecabd6553 | ||
|
|
feaa69f825 | ||
|
|
857904a3d9 | ||
|
|
0b37418477 | ||
|
|
f234ecb2c3 | ||
|
|
b449959865 | ||
|
|
8f870403d2 | ||
|
|
d94f702601 | ||
|
|
e78ab8f922 | ||
|
|
6972000293 | ||
|
|
58560f589f | ||
|
|
6e931ac175 | ||
|
|
a7eb5e8115 | ||
|
|
712e4acc66 | ||
|
|
fdcfdffc0b | ||
|
|
74eb8e3e8d | ||
|
|
ae4c764e4f |
20
.github/workflows/codeql-analysis.yml
vendored
20
.github/workflows/codeql-analysis.yml
vendored
@@ -11,14 +11,14 @@
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '30 18 * * 6'
|
||||
#on:
|
||||
# push:
|
||||
# branches: [ master ]
|
||||
# pull_request:
|
||||
# # The branches below must be a subset of the branches above
|
||||
# branches: [ master ]
|
||||
# schedule:
|
||||
# - cron: '30 18 * * 6'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
@@ -38,12 +38,12 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 5.0.100
|
||||
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -4,6 +4,12 @@
|
||||
[submodule "Lidgren.Network"]
|
||||
path = Lidgren.Network/Lidgren.Network
|
||||
url = https://github.com/space-wizards/lidgren-network-gen3.git
|
||||
[submodule "XamlX"]
|
||||
path = XamlX
|
||||
url = https://github.com/space-wizards/XamlX
|
||||
[submodule "Robust.LoaderApi"]
|
||||
path = Robust.LoaderApi
|
||||
url = https://github.com/space-wizards/Robust.LoaderApi.git
|
||||
[submodule "ManagedHttpListener"]
|
||||
path = ManagedHttpListener
|
||||
url = https://github.com/space-wizards/ManagedHttpListener.git
|
||||
|
||||
63
MSBuild/XamlIL.targets
Normal file
63
MSBuild/XamlIL.targets
Normal file
@@ -0,0 +1,63 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0">
|
||||
<PropertyGroup>
|
||||
<RobustUseExternalMSBuild>true</RobustUseExternalMSBuild>
|
||||
<_RobustUseExternalMSBuild>$(RobustUseExternalMSBuild)</_RobustUseExternalMSBuild>
|
||||
<_RobustUseExternalMSBuild Condition="'$(_RobustForceInternalMSBuild)' == 'true'">false</_RobustUseExternalMSBuild>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="**\*.xaml.cs">
|
||||
<DependentUpon>%(Filename)</DependentUpon>
|
||||
</Compile>
|
||||
<EmbeddedResource Include="**\*.xaml"/>
|
||||
<AdditionalFiles Include="**\*.xaml"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.NameGenerator\Robust.Client.NameGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\Robust.Client.Injectors.csproj" ReferenceOutputAssembly="false"/>
|
||||
</ItemGroup>
|
||||
|
||||
<UsingTask
|
||||
Condition="'$(_RobustUseExternalMSBuild)' != 'true' And $(DesignTimeBuild) != true"
|
||||
TaskName="CompileRobustXamlTask"
|
||||
AssemblyFile="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\bin\$(Configuration)\netstandard2.0\Robust.Client.Injectors.dll"/>
|
||||
<Target
|
||||
Name="CompileRobustXaml"
|
||||
Condition="Exists('@(IntermediateAssembly)')"
|
||||
AfterTargets="AfterCompile"
|
||||
Inputs="@(IntermediateAssembly);@(ReferencePathWithRefAssemblies)"
|
||||
Outputs="$(IntermediateOutputPath)XAML/doot">
|
||||
<PropertyGroup>
|
||||
<RobustXamlReferencesTemporaryFilePath Condition="'$(RobustXamlReferencesTemporaryFilePath)' == ''">$(IntermediateOutputPath)XAML/references</RobustXamlReferencesTemporaryFilePath>
|
||||
<RobustXamlOriginalCopyFilePath Condition="'$(RobustXamlOriginalCopyFilePath)' == ''">$(IntermediateOutputPath)XAML/original.dll</RobustXamlOriginalCopyFilePath>
|
||||
</PropertyGroup>
|
||||
<WriteLinesToFile
|
||||
Condition="'$(_RobustForceInternalMSBuild)' != 'true'"
|
||||
File="$(RobustXamlReferencesTemporaryFilePath)"
|
||||
Lines="@(ReferencePathWithRefAssemblies)"
|
||||
Overwrite="true"/>
|
||||
|
||||
<!--
|
||||
UpdateBuildIndicator is done so that we can use MSBuild Inputs and Outputs on the target
|
||||
to avoid unecessary execution of this target
|
||||
Saves compile time if e.g. ONLY Robust.Client changes (Content.Client doesn't have to re-xaml).
|
||||
-->
|
||||
<CompileRobustXamlTask
|
||||
Condition="'$(_RobustUseExternalMSBuild)' != 'true'"
|
||||
AssemblyFile="@(IntermediateAssembly)"
|
||||
ReferencesFilePath="$(RobustXamlReferencesTemporaryFilePath)"
|
||||
OriginalCopyPath="$(RobustXamlOriginalCopyFilePath)"
|
||||
ProjectDirectory="$(MSBuildProjectDirectory)"
|
||||
AssemblyOriginatorKeyFile="$(AssemblyOriginatorKeyFile)"
|
||||
SignAssembly="$(SignAssembly)"
|
||||
DelaySign="$(DelaySign)"
|
||||
UpdateBuildIndicator="$(IntermediateOutputPath)XAML/doot"/>
|
||||
|
||||
<PropertyGroup>
|
||||
<DOTNET_HOST_PATH Condition="'$(DOTNET_HOST_PATH)' == ''">dotnet</DOTNET_HOST_PATH>
|
||||
</PropertyGroup>
|
||||
<Exec
|
||||
Condition="'$(_RobustUseExternalMSBuild)' == 'true'"
|
||||
Command=""$(DOTNET_HOST_PATH)" msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileRobustXaml /p:_RobustForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/>
|
||||
</Target>
|
||||
</Project>
|
||||
1
ManagedHttpListener
Submodule
1
ManagedHttpListener
Submodule
Submodule ManagedHttpListener added at f2aa590fec
Submodule NetSerializer updated: 60d23c01f9...1e103e8e29
@@ -17,6 +17,7 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
// On net472, we rely on Mono's DllMap for this. See the .dll.config file.
|
||||
NativeLibrary.SetDllImportResolver(typeof(GLFWNative).Assembly, (name, assembly, path) =>
|
||||
{
|
||||
// Please keep in sync with what Robust.Shared/DllMapHelper.cs does.
|
||||
if (name != "glfw3.dll")
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
|
||||
@@ -13,7 +13,7 @@ highp float createOcclusion(highp vec2 diff)
|
||||
// Change soft shadow size based on distance from primary occluder.
|
||||
highp float distRatio = (ourDist - occlDist.x) / occlDist.x / 2.0;
|
||||
|
||||
perpendicular *= distRatio;
|
||||
perpendicular *= distRatio * lightSoftness;
|
||||
|
||||
// Totally not hacky PCF on top of VSM.
|
||||
highp float occlusion = smoothstep(0.1, 1.0, ChebyshevUpperBound(occlDist, ourDist));
|
||||
|
||||
@@ -13,6 +13,7 @@ uniform highp vec4 lightColor;
|
||||
uniform highp vec2 lightCenter;
|
||||
uniform highp float lightRange;
|
||||
uniform highp float lightPower;
|
||||
uniform highp float lightSoftness;
|
||||
uniform highp float lightIndex;
|
||||
uniform sampler2D shadowMap;
|
||||
|
||||
|
||||
101
Robust.Client.Injectors/CompileRobustXamlTask.cs
Normal file
101
Robust.Client.Injectors/CompileRobustXamlTask.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Build.Framework;
|
||||
|
||||
namespace Robust.Build.Tasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Based on https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
|
||||
/// </summary>
|
||||
public class CompileRobustXamlTask : ITask
|
||||
{
|
||||
public bool Execute()
|
||||
{
|
||||
//Debugger.Launch();
|
||||
OutputPath = OutputPath ?? AssemblyFile;
|
||||
var outputPdb = GetPdbPath(OutputPath);
|
||||
var input = AssemblyFile;
|
||||
var inputPdb = GetPdbPath(input);
|
||||
// Make a copy and delete the original file to prevent MSBuild from thinking that everything is OK
|
||||
if (OriginalCopyPath != null)
|
||||
{
|
||||
File.Copy(AssemblyFile, OriginalCopyPath, true);
|
||||
input = OriginalCopyPath;
|
||||
File.Delete(AssemblyFile);
|
||||
|
||||
if (File.Exists(inputPdb))
|
||||
{
|
||||
var copyPdb = GetPdbPath(OriginalCopyPath);
|
||||
File.Copy(inputPdb, copyPdb, true);
|
||||
File.Delete(inputPdb);
|
||||
inputPdb = copyPdb;
|
||||
}
|
||||
}
|
||||
|
||||
var msg = $"CompileRobustXamlTask -> AssemblyFile:{AssemblyFile}, ProjectDirectory:{ProjectDirectory}, OutputPath:{OutputPath}";
|
||||
BuildEngine.LogMessage(msg, MessageImportance.High);
|
||||
|
||||
var res = XamlCompiler.Compile(BuildEngine, input,
|
||||
File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
|
||||
ProjectDirectory, OutputPath,
|
||||
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null);
|
||||
if (!res.success)
|
||||
return false;
|
||||
if (!res.writtentofile)
|
||||
{
|
||||
File.Copy(input, OutputPath, true);
|
||||
if(File.Exists(inputPdb))
|
||||
File.Copy(inputPdb, outputPdb, true);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(UpdateBuildIndicator))
|
||||
{
|
||||
if (!File.Exists(UpdateBuildIndicator))
|
||||
{
|
||||
File.Create(UpdateBuildIndicator).Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
File.SetLastWriteTime(UpdateBuildIndicator, DateTime.Now);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[Required]
|
||||
public string ReferencesFilePath { get; set; }
|
||||
|
||||
[Required]
|
||||
public string ProjectDirectory { get; set; }
|
||||
|
||||
[Required]
|
||||
public string AssemblyFile { get; set; }
|
||||
|
||||
[Required]
|
||||
public string OriginalCopyPath { get; set; }
|
||||
|
||||
public string OutputPath { get; set; }
|
||||
public string UpdateBuildIndicator { get; set; }
|
||||
|
||||
public string AssemblyOriginatorKeyFile { get; set; }
|
||||
public bool SignAssembly { get; set; }
|
||||
public bool DelaySign { get; set; }
|
||||
|
||||
// shamelessly copied from avalonia
|
||||
string GetPdbPath(string p)
|
||||
{
|
||||
var d = Path.GetDirectoryName(p);
|
||||
var f = Path.GetFileNameWithoutExtension(p);
|
||||
var rv = f + ".pdb";
|
||||
if (d != null)
|
||||
rv = Path.Combine(d, rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public IBuildEngine BuildEngine { get; set; }
|
||||
public ITaskHost HostObject { get; set; }
|
||||
}
|
||||
}
|
||||
16
Robust.Client.Injectors/Extensions.cs
Normal file
16
Robust.Client.Injectors/Extensions.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Microsoft.Build.Framework;
|
||||
|
||||
namespace Robust.Build.Tasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Taken from https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/Extensions.cs
|
||||
/// </summary>
|
||||
public static class Extensions
|
||||
{
|
||||
//shamefully copied from avalonia
|
||||
public static void LogMessage(this IBuildEngine engine, string message, MessageImportance imp)
|
||||
{
|
||||
engine.LogMessageEvent(new BuildMessageEventArgs(message, "", "Avalonia", imp));
|
||||
}
|
||||
}
|
||||
}
|
||||
62
Robust.Client.Injectors/Program.cs
Normal file
62
Robust.Client.Injectors/Program.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using Microsoft.Build.Framework;
|
||||
|
||||
namespace Robust.Build.Tasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Based on https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/Program.cs
|
||||
/// </summary>
|
||||
class Program
|
||||
{
|
||||
static int Main(string[] args)
|
||||
{
|
||||
if (args.Length != 3)
|
||||
{
|
||||
Console.Error.WriteLine("expected: input references output");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return new CompileRobustXamlTask
|
||||
{
|
||||
AssemblyFile = args[0],
|
||||
ReferencesFilePath = args[1],
|
||||
OutputPath = args[2],
|
||||
BuildEngine = new ConsoleBuildEngine(),
|
||||
ProjectDirectory = Directory.GetCurrentDirectory()
|
||||
}.Execute() ? 0 : 2;
|
||||
}
|
||||
}
|
||||
|
||||
class ConsoleBuildEngine : IBuildEngine
|
||||
{
|
||||
public void LogErrorEvent(BuildErrorEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"ERROR: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
|
||||
}
|
||||
|
||||
public void LogWarningEvent(BuildWarningEventArgs e)
|
||||
{
|
||||
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($"MESSAGE: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
|
||||
}
|
||||
|
||||
public void LogCustomEvent(CustomBuildEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"CUSTOM: {e.Message}");
|
||||
}
|
||||
|
||||
public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties,
|
||||
IDictionary targetOutputs) => throw new NotSupportedException();
|
||||
|
||||
public bool ContinueOnError { get; }
|
||||
public int LineNumberOfTaskNode { get; }
|
||||
public int ColumnNumberOfTaskNode { get; }
|
||||
public string ProjectFileOfTaskNode { get; }
|
||||
}
|
||||
}
|
||||
16
Robust.Client.Injectors/Robust.Client.Injectors.csproj
Normal file
16
Robust.Client.Injectors/Robust.Client.Injectors.csproj
Normal file
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build.Framework" Version="16.8.0" />
|
||||
<PackageReference Include="Mono.Cecil" Version="0.11.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\XamlX\src\XamlX.IL.Cecil\XamlX.IL.Cecil.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
201
Robust.Client.Injectors/RobustXamlILCompiler.cs
Normal file
201
Robust.Client.Injectors/RobustXamlILCompiler.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
using System.Linq;
|
||||
using XamlX.Ast;
|
||||
using XamlX.Emit;
|
||||
using XamlX.IL;
|
||||
using XamlX.Transform;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Robust.Build.Tasks
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public class RobustXamlILCompiler : XamlILCompiler
|
||||
{
|
||||
public RobustXamlILCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings, bool fillWithDefaults) : base(configuration, emitMappings, fillWithDefaults)
|
||||
{
|
||||
Transformers.Add(new AddNameScopeRegistration());
|
||||
Transformers.Add(new RobustMarkRootObjectScopeNode());
|
||||
|
||||
Emitters.Add(new AddNameScopeRegistration.Emitter());
|
||||
Emitters.Add(new RobustMarkRootObjectScopeNode.Emitter());
|
||||
}
|
||||
|
||||
class AddNameScopeRegistration : IXamlAstTransformer
|
||||
{
|
||||
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
|
||||
{
|
||||
if (node is XamlPropertyAssignmentNode pa)
|
||||
{
|
||||
if (pa.Property.Name == "Name"
|
||||
&& pa.Property.DeclaringType.FullName == "Robust.Client.UserInterface.Control")
|
||||
{
|
||||
if (context.ParentNodes().FirstOrDefault() is XamlManipulationGroupNode mg
|
||||
&& mg.Children.OfType<RobustNameScopeRegistrationXamlIlNode>().Any())
|
||||
return node;
|
||||
|
||||
IXamlAstValueNode value = null;
|
||||
for (var c = 0; c < pa.Values.Count; c++)
|
||||
if (pa.Values[c].Type.GetClrType().Equals(context.Configuration.WellKnownTypes.String))
|
||||
{
|
||||
value = pa.Values[c];
|
||||
if (!(value is XamlAstTextNode))
|
||||
{
|
||||
var local = new XamlAstCompilerLocalNode(value);
|
||||
// Wrap original in local initialization
|
||||
pa.Values[c] = new XamlAstLocalInitializationNodeEmitter(value, value, local);
|
||||
// Use local
|
||||
value = local;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
var objectType = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().FirstOrDefault()?.Type.GetClrType();
|
||||
return new XamlManipulationGroupNode(pa)
|
||||
{
|
||||
Children =
|
||||
{
|
||||
pa,
|
||||
new RobustNameScopeRegistrationXamlIlNode(value, objectType)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
/*else if (pa.Property.CustomAttributes.Select(attr => attr.Type).Intersect(context.Configuration.TypeMappings.DeferredContentPropertyAttributes).Any())
|
||||
{
|
||||
pa.Values[pa.Values.Count - 1] =
|
||||
new NestedScopeMetadataNode(pa.Values[pa.Values.Count - 1]);
|
||||
}*/
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
class RobustNameScopeRegistrationXamlIlNode : XamlAstNode, IXamlAstManipulationNode
|
||||
{
|
||||
public IXamlAstValueNode Name { get; set; }
|
||||
public IXamlType TargetType { get; }
|
||||
|
||||
public RobustNameScopeRegistrationXamlIlNode(IXamlAstValueNode name, IXamlType targetType) : base(name)
|
||||
{
|
||||
TargetType = targetType;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public override void VisitChildren(IXamlAstVisitor visitor)
|
||||
=> Name = (IXamlAstValueNode)Name.Visit(visitor);
|
||||
}
|
||||
|
||||
internal class Emitter : IXamlAstLocalsNodeEmitter<IXamlILEmitter, XamlILNodeEmitResult>
|
||||
{
|
||||
public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContextWithLocals<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
|
||||
{
|
||||
if (node is RobustNameScopeRegistrationXamlIlNode registration)
|
||||
{
|
||||
|
||||
var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
|
||||
f.Name == XamlCompiler.ContextNameScopeFieldName);
|
||||
var namescopeRegisterFunction = context.Configuration.TypeSystem
|
||||
.FindType("Robust.Client.UserInterface.XAML.NameScope").Methods
|
||||
.First(m => m.Name == "Register");
|
||||
|
||||
using (var targetLoc = context.GetLocalOfType(context.Configuration.TypeSystem.FindType("Robust.Client.UserInterface.Control")))
|
||||
{
|
||||
|
||||
codeGen
|
||||
// var target = {pop}
|
||||
.Stloc(targetLoc.Local)
|
||||
// _context.NameScope.Register(Name, target)
|
||||
.Ldloc(context.ContextLocal)
|
||||
.Ldfld(scopeField);
|
||||
|
||||
context.Emit(registration.Name, codeGen, registration.Name.Type.GetClrType());
|
||||
|
||||
codeGen
|
||||
.Ldloc(targetLoc.Local)
|
||||
.EmitCall(namescopeRegisterFunction, true);
|
||||
}
|
||||
|
||||
return XamlILNodeEmitResult.Void(1);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RobustMarkRootObjectScopeNode : IXamlAstTransformer
|
||||
{
|
||||
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
|
||||
{
|
||||
if (!context.ParentNodes().Any()
|
||||
&& node is XamlValueWithManipulationNode mnode)
|
||||
{
|
||||
mnode.Manipulation = new XamlManipulationGroupNode(mnode,
|
||||
new[]
|
||||
{
|
||||
mnode.Manipulation,
|
||||
new HandleRootObjectScopeNode(mnode)
|
||||
});
|
||||
}
|
||||
return node;
|
||||
}
|
||||
class HandleRootObjectScopeNode : XamlAstNode, IXamlAstManipulationNode
|
||||
{
|
||||
public HandleRootObjectScopeNode(IXamlLineInfo lineInfo) : base(lineInfo)
|
||||
{
|
||||
}
|
||||
}
|
||||
internal class Emitter : IXamlILAstNodeEmitter
|
||||
{
|
||||
public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
|
||||
{
|
||||
if (!(node is HandleRootObjectScopeNode))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var controlType = context.Configuration.TypeSystem.FindType("Robust.Client.UserInterface.Control");
|
||||
|
||||
var next = codeGen.DefineLabel();
|
||||
var dontAbsorb = codeGen.DefineLabel();
|
||||
var end = codeGen.DefineLabel();
|
||||
var contextScopeField = context.RuntimeContext.ContextType.Fields.First(f =>
|
||||
f.Name == XamlCompiler.ContextNameScopeFieldName);
|
||||
var controlNameScopeField = controlType.Fields.First(f => f.Name == "NameScope");
|
||||
var nameScopeType = context.Configuration.TypeSystem
|
||||
.FindType("Robust.Client.UserInterface.XAML.NameScope");
|
||||
var nameScopeCompleteMethod = nameScopeType.Methods.First(m => m.Name == "Complete");
|
||||
var nameScopeAbsorbMethod = nameScopeType.Methods.First(m => m.Name == "Absorb");
|
||||
using (var local = codeGen.LocalsPool.GetLocal(controlType))
|
||||
{
|
||||
codeGen
|
||||
.Isinst(controlType)
|
||||
.Dup()
|
||||
.Stloc(local.Local) //store control in local field
|
||||
.Brfalse(next) //if control is null, move to next (this should never happen but whatev, avalonia does it)
|
||||
.Ldloc(context.ContextLocal)
|
||||
.Ldfld(contextScopeField)
|
||||
.Ldloc(local.Local) //load control from local field
|
||||
.Ldfld(controlNameScopeField) //load namescope field from control
|
||||
.EmitCall(nameScopeAbsorbMethod, true)
|
||||
.Ldloc(local.Local) //load control
|
||||
.Ldloc(context.ContextLocal) //load contextObject
|
||||
.Ldfld(contextScopeField) //load namescope field from context obj
|
||||
.Stfld(controlNameScopeField) //store namescope field in control
|
||||
.MarkLabel(next)
|
||||
.Ldloc(context.ContextLocal)
|
||||
.Ldfld(contextScopeField)
|
||||
.EmitCall(nameScopeCompleteMethod, true); //set the namescope as complete
|
||||
}
|
||||
|
||||
return XamlILNodeEmitResult.Void(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
78
Robust.Client.Injectors/XamlCompiler.Helpers.cs
Normal file
78
Robust.Client.Injectors/XamlCompiler.Helpers.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Collections.Generic;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Robust.Build.Tasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpers taken from:
|
||||
/// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
|
||||
/// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
|
||||
/// </summary>
|
||||
public partial class XamlCompiler
|
||||
{
|
||||
static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml")
|
||||
|| r.Name.ToLowerInvariant().EndsWith(".paml")
|
||||
|| r.Name.ToLowerInvariant().EndsWith(".axaml");
|
||||
|
||||
private static bool MatchThisCall(Collection<Instruction> instructions, int idx)
|
||||
{
|
||||
var i = instructions[idx];
|
||||
// A "normal" way of passing `this` to a static method:
|
||||
|
||||
// ldarg.0
|
||||
// call void [Avalonia.Markup.Xaml]Avalonia.Markup.Xaml.AvaloniaXamlLoader::Load(object)
|
||||
|
||||
return i.OpCode == OpCodes.Ldarg_0 || (i.OpCode == OpCodes.Ldarg && i.Operand?.Equals(0) == true);
|
||||
}
|
||||
|
||||
interface IResource : IFileSource
|
||||
{
|
||||
string Uri { get; }
|
||||
string Name { get; }
|
||||
void Remove();
|
||||
|
||||
}
|
||||
|
||||
interface IResourceGroup
|
||||
{
|
||||
string Name { get; }
|
||||
IEnumerable<IResource> Resources { get; }
|
||||
}
|
||||
|
||||
class EmbeddedResources : IResourceGroup
|
||||
{
|
||||
private readonly AssemblyDefinition _asm;
|
||||
public string Name => "EmbeddedResource";
|
||||
|
||||
public IEnumerable<IResource> Resources => _asm.MainModule.Resources.OfType<EmbeddedResource>()
|
||||
.Select(r => new WrappedResource(_asm, r)).ToList();
|
||||
|
||||
public EmbeddedResources(AssemblyDefinition asm)
|
||||
{
|
||||
_asm = asm;
|
||||
}
|
||||
class WrappedResource : IResource
|
||||
{
|
||||
private readonly AssemblyDefinition _asm;
|
||||
private readonly EmbeddedResource _res;
|
||||
|
||||
public WrappedResource(AssemblyDefinition asm, EmbeddedResource res)
|
||||
{
|
||||
_asm = asm;
|
||||
_res = res;
|
||||
}
|
||||
|
||||
public string Uri => $"resm:{Name}?assembly={_asm.Name.Name}";
|
||||
public string Name => _res.Name;
|
||||
public string FilePath => Name;
|
||||
public byte[] FileContents => _res.GetResourceData();
|
||||
|
||||
public void Remove() => _asm.MainModule.Resources.Remove(_res);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
327
Robust.Client.Injectors/XamlCompiler.cs
Normal file
327
Robust.Client.Injectors/XamlCompiler.cs
Normal file
@@ -0,0 +1,327 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Build.Framework;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
using XamlX;
|
||||
using XamlX.Ast;
|
||||
using XamlX.Emit;
|
||||
using XamlX.IL;
|
||||
using XamlX.Parsers;
|
||||
using XamlX.Transform;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Robust.Build.Tasks
|
||||
{
|
||||
/// <summary>
|
||||
/// Based on https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
|
||||
/// Adjusted for our UI-Framework
|
||||
/// </summary>
|
||||
public partial class XamlCompiler
|
||||
{
|
||||
public static (bool success, bool writtentofile) Compile(IBuildEngine engine, string input, string[] references,
|
||||
string projectDirectory, string output, string strongNameKey)
|
||||
{
|
||||
var typeSystem = new CecilTypeSystem(references
|
||||
.Where(r => !r.ToLowerInvariant().EndsWith("robust.build.tasks.dll"))
|
||||
.Concat(new[] { input }), input);
|
||||
|
||||
var asm = typeSystem.TargetAssemblyDefinition;
|
||||
|
||||
if (asm.MainModule.GetType("CompiledRobustXaml", "XamlIlContext") != null)
|
||||
{
|
||||
// If this type exists, the assembly has already been processed by us.
|
||||
// Do not run again, it would corrupt the file.
|
||||
// This *shouldn't* be possible due to Inputs/Outputs dependencies in the build system,
|
||||
// but better safe than sorry eh?
|
||||
engine.LogWarningEvent(new BuildWarningEventArgs("XAMLIL", "", "", 0, 0, 0, 0, "Ran twice on same assembly file; ignoring.", "", ""));
|
||||
return (true, false);
|
||||
}
|
||||
|
||||
var compileRes = CompileCore(engine, typeSystem);
|
||||
if (compileRes == null)
|
||||
return (true, false);
|
||||
if (compileRes == false)
|
||||
return (false, false);
|
||||
|
||||
var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols };
|
||||
if (!string.IsNullOrWhiteSpace(strongNameKey))
|
||||
writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
|
||||
|
||||
asm.Write(output, writerParameters);
|
||||
|
||||
return (true, true);
|
||||
|
||||
}
|
||||
|
||||
static bool? CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem)
|
||||
{
|
||||
var asm = typeSystem.TargetAssemblyDefinition;
|
||||
var embrsc = new EmbeddedResources(asm);
|
||||
|
||||
if (embrsc.Resources.Count(CheckXamlName) == 0)
|
||||
// Nothing to do
|
||||
return null;
|
||||
|
||||
var xamlLanguage = new XamlLanguageTypeMappings(typeSystem)
|
||||
{
|
||||
XmlnsAttributes =
|
||||
{
|
||||
typeSystem.GetType("Robust.Client.UserInterface.XAML.XmlnsDefinitionAttribute"),
|
||||
|
||||
},
|
||||
ContentAttributes =
|
||||
{
|
||||
typeSystem.GetType("Robust.Client.UserInterface.XAML.ContentAttribute")
|
||||
},
|
||||
UsableDuringInitializationAttributes =
|
||||
{
|
||||
typeSystem.GetType("Robust.Client.UserInterface.XAML.UsableDuringInitializationAttribute")
|
||||
},
|
||||
DeferredContentPropertyAttributes =
|
||||
{
|
||||
typeSystem.GetType("Robust.Client.UserInterface.XAML.DeferredContentAttribute")
|
||||
},
|
||||
RootObjectProvider = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestRootObjectProvider"),
|
||||
UriContextProvider = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestUriContext"),
|
||||
ProvideValueTarget = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestProvideValueTarget"),
|
||||
};
|
||||
var emitConfig = new XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult>
|
||||
{
|
||||
ContextTypeBuilderCallback = (b,c) => EmitNameScopeField(xamlLanguage, typeSystem, b, c)
|
||||
};
|
||||
|
||||
var transformerconfig = new TransformerConfiguration(
|
||||
typeSystem,
|
||||
typeSystem.TargetAssembly,
|
||||
xamlLanguage,
|
||||
XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage));
|
||||
|
||||
var contextDef = new TypeDefinition("CompiledRobustXaml", "XamlIlContext",
|
||||
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
|
||||
asm.MainModule.Types.Add(contextDef);
|
||||
var contextClass = XamlILContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
|
||||
xamlLanguage, emitConfig);
|
||||
|
||||
var compiler =
|
||||
new RobustXamlILCompiler(transformerconfig, emitConfig, true);
|
||||
|
||||
var loaderDispatcherDef = new TypeDefinition("CompiledRobustXaml", "!XamlLoader",
|
||||
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
|
||||
|
||||
var loaderDispatcherMethod = new MethodDefinition("TryLoad",
|
||||
MethodAttributes.Static | MethodAttributes.Public,
|
||||
asm.MainModule.TypeSystem.Object)
|
||||
{
|
||||
Parameters = {new ParameterDefinition(asm.MainModule.TypeSystem.String)}
|
||||
};
|
||||
loaderDispatcherDef.Methods.Add(loaderDispatcherMethod);
|
||||
asm.MainModule.Types.Add(loaderDispatcherDef);
|
||||
|
||||
var stringEquals = asm.MainModule.ImportReference(asm.MainModule.TypeSystem.String.Resolve().Methods.First(
|
||||
m =>
|
||||
m.IsStatic && m.Name == "Equals" && m.Parameters.Count == 2 &&
|
||||
m.ReturnType.FullName == "System.Boolean"
|
||||
&& m.Parameters[0].ParameterType.FullName == "System.String"
|
||||
&& m.Parameters[1].ParameterType.FullName == "System.String"));
|
||||
|
||||
bool CompileGroup(IResourceGroup group)
|
||||
{
|
||||
var typeDef = new TypeDefinition("CompiledRobustXaml", "!" + group.Name, TypeAttributes.Class,
|
||||
asm.MainModule.TypeSystem.Object);
|
||||
|
||||
//typeDef.CustomAttributes.Add(new CustomAttribute(ed));
|
||||
asm.MainModule.Types.Add(typeDef);
|
||||
var builder = typeSystem.CreateTypeBuilder(typeDef);
|
||||
|
||||
foreach (var res in group.Resources.Where(CheckXamlName))
|
||||
{
|
||||
try
|
||||
{
|
||||
engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", MessageImportance.Low);
|
||||
|
||||
var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
|
||||
var parsed = XDocumentXamlParser.Parse(xaml);
|
||||
|
||||
var initialRoot = (XamlAstObjectNode) parsed.Root;
|
||||
|
||||
var classDirective = initialRoot.Children.OfType<XamlAstXmlDirective>()
|
||||
.FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class");
|
||||
string classname;
|
||||
if (classDirective != null && classDirective.Values[0] is XamlAstTextNode tn)
|
||||
{
|
||||
classname = tn.Text;
|
||||
}
|
||||
else
|
||||
{
|
||||
classname = res.Name.Replace(".xaml","");
|
||||
}
|
||||
|
||||
var classType = typeSystem.TargetAssembly.FindType(classname);
|
||||
if (classType == null)
|
||||
throw new Exception($"Unable to find type '{classname}'");
|
||||
|
||||
compiler.Transform(parsed);
|
||||
|
||||
var populateName = $"Populate:{res.Name}";
|
||||
var buildName = $"Build:{res.Name}";
|
||||
|
||||
var classTypeDefinition = typeSystem.GetTypeReference(classType).Resolve();
|
||||
|
||||
var populateBuilder = typeSystem.CreateTypeBuilder(classTypeDefinition);
|
||||
|
||||
compiler.Compile(parsed, contextClass,
|
||||
compiler.DefinePopulateMethod(populateBuilder, parsed, populateName,
|
||||
classTypeDefinition == null),
|
||||
compiler.DefineBuildMethod(builder, parsed, buildName, true),
|
||||
null,
|
||||
(closureName, closureBaseType) =>
|
||||
populateBuilder.DefineSubType(closureBaseType, closureName, false),
|
||||
res.Uri, res
|
||||
);
|
||||
|
||||
//add compiled populate method
|
||||
var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve().Methods
|
||||
.First(m => m.Name == populateName);
|
||||
|
||||
const string TrampolineName = "!XamlIlPopulateTrampoline";
|
||||
var trampoline = new MethodDefinition(TrampolineName,
|
||||
MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void);
|
||||
trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition));
|
||||
classTypeDefinition.Methods.Add(trampoline);
|
||||
|
||||
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull));
|
||||
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
|
||||
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, compiledPopulateMethod));
|
||||
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
|
||||
|
||||
var foundXamlLoader = false;
|
||||
// Find RobustXamlLoader.Load(this) and replace it with !XamlIlPopulateTrampoline(this)
|
||||
foreach (var method in classTypeDefinition.Methods
|
||||
.Where(m => !m.Attributes.HasFlag(MethodAttributes.Static)))
|
||||
{
|
||||
var i = method.Body.Instructions;
|
||||
for (var c = 1; c < i.Count; c++)
|
||||
{
|
||||
if (i[c].OpCode == OpCodes.Call)
|
||||
{
|
||||
var op = i[c].Operand as MethodReference;
|
||||
|
||||
if (op != null
|
||||
&& op.Name == TrampolineName)
|
||||
{
|
||||
foundXamlLoader = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (op != null
|
||||
&& op.Name == "Load"
|
||||
&& op.Parameters.Count == 1
|
||||
&& op.Parameters[0].ParameterType.FullName == "System.Object"
|
||||
&& op.DeclaringType.FullName == "Robust.Client.UserInterface.XAML.RobustXamlLoader")
|
||||
{
|
||||
if (MatchThisCall(i, c - 1))
|
||||
{
|
||||
i[c].Operand = trampoline;
|
||||
foundXamlLoader = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundXamlLoader)
|
||||
{
|
||||
var ctors = classTypeDefinition.GetConstructors()
|
||||
.Where(c => !c.IsStatic).ToList();
|
||||
// We can inject xaml loader into default constructor
|
||||
if (ctors.Count == 1 && ctors[0].Body.Instructions.Count(o=>o.OpCode != OpCodes.Nop) == 3)
|
||||
{
|
||||
var i = ctors[0].Body.Instructions;
|
||||
var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret));
|
||||
i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampoline));
|
||||
i.Insert(retIdx, Instruction.Create(OpCodes.Ldarg_0));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidProgramException(
|
||||
$"No call to RobustXamlLoader.Load(this) call found anywhere in the type {classType.FullName} and type seems to have custom constructors.");
|
||||
}
|
||||
}
|
||||
|
||||
//add compiled build method
|
||||
var compiledBuildMethod = typeSystem.GetTypeReference(builder).Resolve().Methods
|
||||
.First(m => m.Name == buildName);
|
||||
var parameterlessCtor = classTypeDefinition.GetConstructors()
|
||||
.FirstOrDefault(c => c.IsPublic && !c.IsStatic && !c.HasParameters);
|
||||
|
||||
if (compiledBuildMethod != null && parameterlessCtor != null)
|
||||
{
|
||||
var i = loaderDispatcherMethod.Body.Instructions;
|
||||
var nop = Instruction.Create(OpCodes.Nop);
|
||||
i.Add(Instruction.Create(OpCodes.Ldarg_0));
|
||||
i.Add(Instruction.Create(OpCodes.Ldstr, res.Uri));
|
||||
i.Add(Instruction.Create(OpCodes.Call, stringEquals));
|
||||
i.Add(Instruction.Create(OpCodes.Brfalse, nop));
|
||||
if (parameterlessCtor != null)
|
||||
i.Add(Instruction.Create(OpCodes.Newobj, parameterlessCtor));
|
||||
else
|
||||
{
|
||||
i.Add(Instruction.Create(OpCodes.Call, compiledBuildMethod));
|
||||
}
|
||||
|
||||
i.Add(Instruction.Create(OpCodes.Ret));
|
||||
i.Add(nop);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
engine.LogWarningEvent(new BuildWarningEventArgs("XAMLIL", "", res.Uri, 0, 0, 0, 0,
|
||||
e.ToString(), "", "CompileRobustXaml"));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (embrsc.Resources.Count(CheckXamlName) != 0)
|
||||
{
|
||||
if (!CompileGroup(embrsc))
|
||||
return false;
|
||||
}
|
||||
|
||||
loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull));
|
||||
loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
|
||||
return true;
|
||||
}
|
||||
|
||||
public const string ContextNameScopeFieldName = "RobustNameScope";
|
||||
|
||||
private static void EmitNameScopeField(XamlLanguageTypeMappings xamlLanguage, CecilTypeSystem typeSystem, IXamlTypeBuilder<IXamlILEmitter> typeBuilder, IXamlILEmitter constructor)
|
||||
{
|
||||
var nameScopeType = typeSystem.FindType("Robust.Client.UserInterface.XAML.NameScope");
|
||||
var field = typeBuilder.DefineField(nameScopeType,
|
||||
ContextNameScopeFieldName, true, false);
|
||||
constructor
|
||||
.Ldarg_0()
|
||||
.Newobj(nameScopeType.GetConstructor())
|
||||
.Stfld(field);
|
||||
}
|
||||
}
|
||||
|
||||
interface IResource : IFileSource
|
||||
{
|
||||
string Uri { get; }
|
||||
string Name { get; }
|
||||
void Remove();
|
||||
|
||||
}
|
||||
|
||||
interface IResourceGroup
|
||||
{
|
||||
string Name { get; }
|
||||
IEnumerable<IResource> Resources { get; }
|
||||
}
|
||||
}
|
||||
21
Robust.Client.NameGenerator/NameReferenceSyntaxReceiver.cs
Normal file
21
Robust.Client.NameGenerator/NameReferenceSyntaxReceiver.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Robust.Client.NameGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Taken from https://github.com/AvaloniaUI/Avalonia.NameGenerator/blob/ecc9677a23de5cbc90af07ccac14e31c0da41d6a/src/Avalonia.NameGenerator/NameReferenceSyntaxReceiver.cs
|
||||
/// </summary>
|
||||
internal class NameReferenceSyntaxReceiver : ISyntaxReceiver
|
||||
{
|
||||
public List<ClassDeclarationSyntax> CandidateClasses { get; } = new List<ClassDeclarationSyntax>();
|
||||
|
||||
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||
{
|
||||
if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax &&
|
||||
classDeclarationSyntax.AttributeLists.Count > 0)
|
||||
CandidateClasses.Add(classDeclarationSyntax);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Link="XamlX\filename" Include="../XamlX/src/XamlX/**/*.cs" />
|
||||
<Compile Remove="../XamlX/src/XamlX/**/SreTypeSystem.cs" />
|
||||
<Compile Remove="../XamlX/src/XamlX/obj/**" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
287
Robust.Client.NameGenerator/RoslynTypeSystem.cs
Normal file
287
Robust.Client.NameGenerator/RoslynTypeSystem.cs
Normal file
@@ -0,0 +1,287 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Robust.Client.NameGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Taken from https://github.com/AvaloniaUI/Avalonia.NameGenerator/blob/ecc9677a23de5cbc90af07ccac14e31c0da41d6a/src/Avalonia.NameGenerator/Infrastructure/RoslynTypeSystem.cs
|
||||
/// </summary>
|
||||
public class RoslynTypeSystem : IXamlTypeSystem
|
||||
{
|
||||
private readonly List<IXamlAssembly> _assemblies = new List<IXamlAssembly>();
|
||||
|
||||
public RoslynTypeSystem(CSharpCompilation compilation)
|
||||
{
|
||||
_assemblies.Add(new RoslynAssembly(compilation.Assembly));
|
||||
|
||||
var assemblySymbols = compilation
|
||||
.References
|
||||
.Select(compilation.GetAssemblyOrModuleSymbol)
|
||||
.OfType<IAssemblySymbol>()
|
||||
.Select(assembly => new RoslynAssembly(assembly))
|
||||
.ToList();
|
||||
|
||||
_assemblies.AddRange(assemblySymbols);
|
||||
}
|
||||
|
||||
public IReadOnlyList<IXamlAssembly> Assemblies => _assemblies;
|
||||
|
||||
public IXamlAssembly FindAssembly(string substring) => _assemblies[0];
|
||||
|
||||
public IXamlType FindType(string name)
|
||||
{
|
||||
foreach (var assembly in _assemblies)
|
||||
{
|
||||
var type = assembly.FindType(name);
|
||||
if (type != null)
|
||||
return type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IXamlType FindType(string name, string assembly)
|
||||
{
|
||||
foreach (var assemblyInstance in _assemblies)
|
||||
{
|
||||
var type = assemblyInstance.FindType(name);
|
||||
if (type != null)
|
||||
return type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class RoslynAssembly : IXamlAssembly
|
||||
{
|
||||
private readonly IAssemblySymbol _symbol;
|
||||
|
||||
public RoslynAssembly(IAssemblySymbol symbol) => _symbol = symbol;
|
||||
|
||||
public bool Equals(IXamlAssembly other) =>
|
||||
other is RoslynAssembly roslynAssembly &&
|
||||
SymbolEqualityComparer.Default.Equals(_symbol, roslynAssembly._symbol);
|
||||
|
||||
public string Name => _symbol.Name;
|
||||
|
||||
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes =>
|
||||
_symbol.GetAttributes()
|
||||
.Select(data => new RoslynAttribute(data, this))
|
||||
.ToList();
|
||||
|
||||
public IXamlType FindType(string fullName)
|
||||
{
|
||||
var type = _symbol.GetTypeByMetadataName(fullName);
|
||||
return type is null ? null : new RoslynType(type, this);
|
||||
}
|
||||
}
|
||||
|
||||
public class RoslynAttribute : IXamlCustomAttribute
|
||||
{
|
||||
private readonly AttributeData _data;
|
||||
private readonly RoslynAssembly _assembly;
|
||||
|
||||
public RoslynAttribute(AttributeData data, RoslynAssembly assembly)
|
||||
{
|
||||
_data = data;
|
||||
_assembly = assembly;
|
||||
}
|
||||
|
||||
public bool Equals(IXamlCustomAttribute other) =>
|
||||
other is RoslynAttribute attribute &&
|
||||
_data == attribute._data;
|
||||
|
||||
public IXamlType Type => new RoslynType(_data.AttributeClass, _assembly);
|
||||
|
||||
public List<object> Parameters =>
|
||||
_data.ConstructorArguments
|
||||
.Select(argument => argument.Value)
|
||||
.ToList();
|
||||
|
||||
public Dictionary<string, object> Properties =>
|
||||
_data.NamedArguments.ToDictionary(
|
||||
pair => pair.Key,
|
||||
pair => pair.Value.Value);
|
||||
}
|
||||
|
||||
public class RoslynType : IXamlType
|
||||
{
|
||||
private static readonly SymbolDisplayFormat SymbolDisplayFormat = new SymbolDisplayFormat(
|
||||
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
|
||||
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters |
|
||||
SymbolDisplayGenericsOptions.IncludeTypeConstraints |
|
||||
SymbolDisplayGenericsOptions.IncludeVariance);
|
||||
|
||||
private readonly RoslynAssembly _assembly;
|
||||
private readonly INamedTypeSymbol _symbol;
|
||||
|
||||
public RoslynType(INamedTypeSymbol symbol, RoslynAssembly assembly)
|
||||
{
|
||||
_symbol = symbol;
|
||||
_assembly = assembly;
|
||||
}
|
||||
|
||||
public bool Equals(IXamlType other) =>
|
||||
other is RoslynType roslynType &&
|
||||
SymbolEqualityComparer.Default.Equals(_symbol, roslynType._symbol);
|
||||
|
||||
public object Id => _symbol;
|
||||
|
||||
public string Name => _symbol.Name;
|
||||
|
||||
public string Namespace => _symbol.ContainingNamespace.ToDisplayString(SymbolDisplayFormat);
|
||||
|
||||
public string FullName => $"{Namespace}.{Name}";
|
||||
|
||||
public IXamlAssembly Assembly => _assembly;
|
||||
|
||||
public IReadOnlyList<IXamlProperty> Properties =>
|
||||
_symbol.GetMembers()
|
||||
.Where(member => member.Kind == SymbolKind.Property)
|
||||
.OfType<IPropertySymbol>()
|
||||
.Select(property => new RoslynProperty(property, _assembly))
|
||||
.ToList();
|
||||
|
||||
public IReadOnlyList<IXamlEventInfo> Events { get; } = new List<IXamlEventInfo>();
|
||||
|
||||
public IReadOnlyList<IXamlField> Fields { get; } = new List<IXamlField>();
|
||||
|
||||
public IReadOnlyList<IXamlMethod> Methods { get; } = new List<IXamlMethod>();
|
||||
|
||||
public IReadOnlyList<IXamlConstructor> Constructors =>
|
||||
_symbol.Constructors
|
||||
.Select(method => new RoslynConstructor(method, _assembly))
|
||||
.ToList();
|
||||
|
||||
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes { get; } = new List<IXamlCustomAttribute>();
|
||||
|
||||
public IReadOnlyList<IXamlType> GenericArguments { get; } = new List<IXamlType>();
|
||||
|
||||
public bool IsAssignableFrom(IXamlType type) => type == this;
|
||||
|
||||
public IXamlType MakeGenericType(IReadOnlyList<IXamlType> typeArguments) => this;
|
||||
|
||||
public IXamlType GenericTypeDefinition => this;
|
||||
|
||||
public bool IsArray => false;
|
||||
|
||||
public IXamlType ArrayElementType { get; } = null;
|
||||
|
||||
public IXamlType MakeArrayType(int dimensions) => null;
|
||||
|
||||
public IXamlType BaseType => _symbol.BaseType == null ? null : new RoslynType(_symbol.BaseType, _assembly);
|
||||
|
||||
public bool IsValueType { get; } = false;
|
||||
|
||||
public bool IsEnum { get; } = false;
|
||||
|
||||
public IReadOnlyList<IXamlType> Interfaces =>
|
||||
_symbol.AllInterfaces
|
||||
.Select(abstraction => new RoslynType(abstraction, _assembly))
|
||||
.ToList();
|
||||
|
||||
public bool IsInterface => _symbol.IsAbstract;
|
||||
|
||||
public IXamlType GetEnumUnderlyingType() => null;
|
||||
|
||||
public IReadOnlyList<IXamlType> GenericParameters { get; } = new List<IXamlType>();
|
||||
}
|
||||
|
||||
public class RoslynConstructor : IXamlConstructor
|
||||
{
|
||||
private readonly IMethodSymbol _symbol;
|
||||
private readonly RoslynAssembly _assembly;
|
||||
|
||||
public RoslynConstructor(IMethodSymbol symbol, RoslynAssembly assembly)
|
||||
{
|
||||
_symbol = symbol;
|
||||
_assembly = assembly;
|
||||
}
|
||||
|
||||
public bool Equals(IXamlConstructor other) =>
|
||||
other is RoslynConstructor roslynConstructor &&
|
||||
SymbolEqualityComparer.Default.Equals(_symbol, roslynConstructor._symbol);
|
||||
|
||||
public bool IsPublic => true;
|
||||
|
||||
public bool IsStatic => false;
|
||||
|
||||
public IReadOnlyList<IXamlType> Parameters =>
|
||||
_symbol.Parameters
|
||||
.Select(parameter => parameter.Type)
|
||||
.OfType<INamedTypeSymbol>()
|
||||
.Select(type => new RoslynType(type, _assembly))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public class RoslynProperty : IXamlProperty
|
||||
{
|
||||
private readonly IPropertySymbol _symbol;
|
||||
private readonly RoslynAssembly _assembly;
|
||||
|
||||
public RoslynProperty(IPropertySymbol symbol, RoslynAssembly assembly)
|
||||
{
|
||||
_symbol = symbol;
|
||||
_assembly = assembly;
|
||||
}
|
||||
|
||||
public bool Equals(IXamlProperty other) =>
|
||||
other is RoslynProperty roslynProperty &&
|
||||
SymbolEqualityComparer.Default.Equals(_symbol, roslynProperty._symbol);
|
||||
|
||||
public string Name => _symbol.Name;
|
||||
|
||||
public IXamlType PropertyType =>
|
||||
_symbol.Type is INamedTypeSymbol namedTypeSymbol
|
||||
? new RoslynType(namedTypeSymbol, _assembly)
|
||||
: null;
|
||||
|
||||
public IXamlMethod Getter => _symbol.GetMethod == null ? null : new RoslynMethod(_symbol.GetMethod, _assembly);
|
||||
|
||||
public IXamlMethod Setter => _symbol.SetMethod == null ? null : new RoslynMethod(_symbol.SetMethod, _assembly);
|
||||
|
||||
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes { get; } = new List<IXamlCustomAttribute>();
|
||||
|
||||
public IReadOnlyList<IXamlType> IndexerParameters { get; } = new List<IXamlType>();
|
||||
}
|
||||
|
||||
public class RoslynMethod : IXamlMethod
|
||||
{
|
||||
private readonly IMethodSymbol _symbol;
|
||||
private readonly RoslynAssembly _assembly;
|
||||
|
||||
public RoslynMethod(IMethodSymbol symbol, RoslynAssembly assembly)
|
||||
{
|
||||
_symbol = symbol;
|
||||
_assembly = assembly;
|
||||
}
|
||||
|
||||
public bool Equals(IXamlMethod other) =>
|
||||
other is RoslynMethod roslynMethod &&
|
||||
SymbolEqualityComparer.Default.Equals(roslynMethod._symbol, _symbol);
|
||||
|
||||
public string Name => _symbol.Name;
|
||||
|
||||
public bool IsPublic => true;
|
||||
|
||||
public bool IsStatic => false;
|
||||
|
||||
public IXamlType ReturnType => new RoslynType((INamedTypeSymbol) _symbol.ReturnType, _assembly);
|
||||
|
||||
public IReadOnlyList<IXamlType> Parameters =>
|
||||
_symbol.Parameters.Select(parameter => parameter.Type)
|
||||
.OfType<INamedTypeSymbol>()
|
||||
.Select(type => new RoslynType(type, _assembly))
|
||||
.ToList();
|
||||
|
||||
public IXamlType DeclaringType => new RoslynType((INamedTypeSymbol)_symbol.ReceiverType, _assembly);
|
||||
|
||||
public IXamlMethod MakeGenericMethod(IReadOnlyList<IXamlType> typeArguments) => null;
|
||||
|
||||
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes { get; } = new List<IXamlCustomAttribute>();
|
||||
}
|
||||
}
|
||||
251
Robust.Client.NameGenerator/XamlUiPartialClassGenerator.cs
Normal file
251
Robust.Client.NameGenerator/XamlUiPartialClassGenerator.cs
Normal file
@@ -0,0 +1,251 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using XamlX.Ast;
|
||||
using XamlX.Emit;
|
||||
using XamlX.IL;
|
||||
using XamlX.Parsers;
|
||||
using XamlX.Transform;
|
||||
using XamlX.Transform.Transformers;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Robust.Client.NameGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Based on https://github.com/AvaloniaUI/Avalonia.NameGenerator/blob/ecc9677a23de5cbc90af07ccac14e31c0da41d6a/src/Avalonia.NameGenerator/NameReferenceGenerator.cs
|
||||
/// Adjusted for our UI-Framework & needs.
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public class XamlUiPartialClassGenerator : ISourceGenerator
|
||||
{
|
||||
private const string AttributeName = "Robust.Client.AutoGenerated.GenerateTypedNameReferencesAttribute";
|
||||
private const string AttributeFile = "GenerateTypedNameReferencesAttribute";
|
||||
private const string AttributeCode = @"// <auto-generated />
|
||||
using System;
|
||||
namespace Robust.Client.AutoGenerated
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class GenerateTypedNameReferencesAttribute : Attribute { }
|
||||
}
|
||||
";
|
||||
|
||||
class NameVisitor : IXamlAstVisitor
|
||||
{
|
||||
private List<(string name, string type)> _names = new List<(string name, string type)>();
|
||||
|
||||
public static List<(string name, string type)> GetNames(IXamlAstNode node)
|
||||
{
|
||||
var visitor = new NameVisitor();
|
||||
node.Visit(visitor);
|
||||
return visitor._names;
|
||||
}
|
||||
|
||||
private bool IsControl(IXamlType type) => type.FullName != "System.Object" &&
|
||||
(type.FullName == "Robust.Client.UserInterface.Control" ||
|
||||
IsControl(type.BaseType));
|
||||
|
||||
public IXamlAstNode Visit(IXamlAstNode node)
|
||||
{
|
||||
if (node is XamlAstObjectNode objectNode)
|
||||
{
|
||||
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;
|
||||
|
||||
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)
|
||||
{
|
||||
var reg = (text.Text, $@"{clrtype.Namespace}.{clrtype.Name}");
|
||||
if (!_names.Contains(reg))
|
||||
{
|
||||
_names.Add(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public void Push(IXamlAstNode node) { }
|
||||
|
||||
public void Pop() { }
|
||||
}
|
||||
|
||||
private static string GenerateSourceCode(
|
||||
INamedTypeSymbol classSymbol,
|
||||
string xamlFile,
|
||||
CSharpCompilation comp)
|
||||
{
|
||||
var className = classSymbol.Name;
|
||||
var nameSpace = classSymbol.ContainingNamespace.ToDisplayString();
|
||||
var parsed = XDocumentXamlParser.Parse(xamlFile);
|
||||
var typeSystem = new RoslynTypeSystem(comp);
|
||||
var compiler =
|
||||
new XamlILCompiler(
|
||||
new TransformerConfiguration(typeSystem, typeSystem.Assemblies[0],
|
||||
new XamlLanguageTypeMappings(typeSystem)),
|
||||
new XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult>(), false);
|
||||
compiler.Transformers.Add(new TypeReferenceResolver());
|
||||
compiler.Transform(parsed);
|
||||
var initialRoot = (XamlAstObjectNode) parsed.Root;
|
||||
var names = NameVisitor.GetNames(initialRoot);
|
||||
//var names = NameVisitor.GetNames((XamlAstObjectNode)XDocumentXamlParser.Parse(xamlFile).Root);
|
||||
var namedControls = names.Select(info => " " +
|
||||
$"protected global::{info.type} {info.name} => " +
|
||||
$"this.FindControl<global::{info.type}>(\"{info.name}\");");
|
||||
return $@"// <auto-generated />
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
namespace {nameSpace}
|
||||
{{
|
||||
partial class {className}
|
||||
{{
|
||||
{string.Join("\n", namedControls)}
|
||||
}}
|
||||
}}
|
||||
";
|
||||
}
|
||||
|
||||
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
{
|
||||
var comp = (CSharpCompilation) context.Compilation;
|
||||
if(comp.GetTypeByMetadataName(AttributeName) == null)
|
||||
context.AddSource(AttributeFile, SourceText.From(AttributeCode, Encoding.UTF8));
|
||||
if (!(context.SyntaxReceiver is NameReferenceSyntaxReceiver receiver))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var symbols = UnpackAnnotatedTypes(context, comp, receiver);
|
||||
if(symbols == null)
|
||||
return;
|
||||
|
||||
foreach (var typeSymbol in symbols)
|
||||
{
|
||||
var xamlFileName = $"{typeSymbol.Name}.xaml";
|
||||
var relevantXamlFile = context.AdditionalFiles.FirstOrDefault(t => t.Path.EndsWith(xamlFileName));
|
||||
|
||||
if (relevantXamlFile == null)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0001",
|
||||
$"Unable to discover the relevant Robust XAML file for {typeSymbol}.",
|
||||
"Unable to discover the relevant Robust XAML file " +
|
||||
$"expected at {xamlFileName}",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
typeSymbol.Locations[0]));
|
||||
return;
|
||||
}
|
||||
|
||||
var txt = relevantXamlFile.GetText()?.ToString();
|
||||
if (txt == null)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0002",
|
||||
$"Unexpected empty Xaml-File was found at {xamlFileName}",
|
||||
"Expected Content due to a Class with the same name being annotated with [GenerateTypedNameReferences].",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
Location.Create(xamlFileName, new TextSpan(0,0), new LinePositionSpan(new LinePosition(0,0),new LinePosition(0,0)))));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var sourceCode = GenerateSourceCode(typeSymbol, txt, comp);
|
||||
context.AddSource($"{typeSymbol.Name}.g.cs", SourceText.From(sourceCode, Encoding.UTF8));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"AXN0003",
|
||||
"Unhandled exception occured while generating typed Name references.",
|
||||
$"Unhandled exception occured while generating typed Name references: {e}",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
typeSymbol.Locations[0]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IReadOnlyList<INamedTypeSymbol> UnpackAnnotatedTypes(in GeneratorExecutionContext context, CSharpCompilation comp, NameReferenceSyntaxReceiver receiver)
|
||||
{
|
||||
var options = (CSharpParseOptions) comp.SyntaxTrees[0].Options;
|
||||
var compilation =
|
||||
comp.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(AttributeCode, Encoding.UTF8), options));
|
||||
var symbols = new List<INamedTypeSymbol>();
|
||||
var attributeSymbol = compilation.GetTypeByMetadataName(AttributeName);
|
||||
foreach (var candidateClass in receiver.CandidateClasses)
|
||||
{
|
||||
var model = compilation.GetSemanticModel(candidateClass.SyntaxTree);
|
||||
var typeSymbol = (INamedTypeSymbol) model.GetDeclaredSymbol(candidateClass);
|
||||
var relevantAttribute = typeSymbol.GetAttributes().FirstOrDefault(attr =>
|
||||
attr.AttributeClass != null &&
|
||||
attr.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default));
|
||||
|
||||
if (relevantAttribute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var isPartial = candidateClass.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword));
|
||||
|
||||
if (isPartial)
|
||||
{
|
||||
symbols.Add(typeSymbol);
|
||||
}
|
||||
else
|
||||
{
|
||||
var missingPartialKeywordMessage =
|
||||
$"The type {typeSymbol.Name} should be declared with the 'partial' keyword " +
|
||||
"as it is annotated with the [GenerateTypedNameReferences] attribute.";
|
||||
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0004",
|
||||
missingPartialKeywordMessage,
|
||||
missingPartialKeywordMessage,
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
Location.None));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterForSyntaxNotifications(() => new NameReferenceSyntaxReceiver());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ namespace Robust.Client.Audio.Midi
|
||||
private const string OsxSoundfont =
|
||||
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
|
||||
|
||||
private const string FallbackSoundfont = "/Resources/Midi/fallback.sf2";
|
||||
private const string FallbackSoundfont = "/Midi/fallback.sf2";
|
||||
|
||||
private readonly ResourceLoaderCallbacks _soundfontLoaderCallbacks = new();
|
||||
|
||||
@@ -207,13 +207,10 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
var renderer = new MidiRenderer(_settings!, soundfontLoader);
|
||||
|
||||
foreach (var file in _resourceManager.ContentFindFiles(new ResourcePath("/Audio/MidiCustom/")))
|
||||
foreach (var file in _resourceManager.ContentFindFiles(("/Audio/MidiCustom/")))
|
||||
{
|
||||
if (file.Extension != "sf2" && file.Extension != "dls") continue;
|
||||
if (_resourceManager.TryGetDiskFilePath(file, out var path))
|
||||
{
|
||||
renderer.LoadSoundfont(path);
|
||||
}
|
||||
renderer.LoadSoundfont(file.ToString());
|
||||
}
|
||||
|
||||
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
|
||||
@@ -382,10 +379,18 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
public override IntPtr Open(string filename)
|
||||
{
|
||||
Stream? stream;
|
||||
if (filename.StartsWith("/Resources/"))
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
{
|
||||
if (!IoCManager.Resolve<IResourceCache>().TryContentFileRead(filename.Substring(10), out stream))
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
Stream? stream;
|
||||
var resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||
var resourcePath = new ResourcePath(filename);
|
||||
|
||||
if (resourcePath.IsRooted && resourceCache.ContentFileExists(filename))
|
||||
{
|
||||
if (!resourceCache.TryContentFileRead(filename, out stream))
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
else if (File.Exists(filename))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net;
|
||||
using Robust.Client.Interfaces;
|
||||
using Robust.Client.Interfaces.Debugging;
|
||||
@@ -7,8 +7,8 @@ using Robust.Client.Interfaces.GameStates;
|
||||
using Robust.Client.Interfaces.Utility;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
@@ -25,7 +25,7 @@ namespace Robust.Client
|
||||
{
|
||||
[Dependency] private readonly IClientNetManager _net = default!;
|
||||
[Dependency] private readonly IPlayerManager _playMan = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IDiscordRichPresence _discord = default!;
|
||||
@@ -50,9 +50,7 @@ namespace Robust.Client
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
{
|
||||
_net.RegisterNetMessage<MsgServerInfo>(MsgServerInfo.NAME, HandleServerInfo);
|
||||
_net.RegisterNetMessage<MsgSetTickRate>(MsgSetTickRate.NAME, HandleSetTickRate);
|
||||
_net.RegisterNetMessage<MsgServerInfoReq>(MsgServerInfoReq.NAME);
|
||||
_net.Connected += OnConnected;
|
||||
_net.ConnectFailed += OnConnectFailed;
|
||||
_net.Disconnect += OnNetDisconnect;
|
||||
@@ -98,9 +96,44 @@ namespace Robust.Client
|
||||
|
||||
private void OnConnected(object? sender, NetChannelArgs args)
|
||||
{
|
||||
// request base info about the server
|
||||
var msgInfo = _net.CreateNetMessage<MsgServerInfoReq>();
|
||||
_net.ClientSendMessage(msgInfo);
|
||||
_configManager.SyncWithServer();
|
||||
_configManager.ReceivedInitialNwVars += OnReceivedClientData;
|
||||
}
|
||||
|
||||
private void OnReceivedClientData(object? sender, EventArgs e)
|
||||
{
|
||||
_configManager.ReceivedInitialNwVars -= OnReceivedClientData;
|
||||
|
||||
var info = GameInfo;
|
||||
|
||||
var serverName = _configManager.GetCVar<string>("game.hostname");
|
||||
if (info == null)
|
||||
{
|
||||
GameInfo = info = new ServerInfo(serverName);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.ServerName = serverName;
|
||||
}
|
||||
|
||||
var maxPlayers = _configManager.GetCVar<int>("game.maxplayers");
|
||||
info.ServerMaxPlayers = maxPlayers;
|
||||
|
||||
var tickrate = _configManager.GetCVar<int>("net.tickrate");
|
||||
info.TickRate = (byte) tickrate;
|
||||
_timing.TickRate = (byte) tickrate;
|
||||
Logger.InfoS("client", $"Tickrate changed to: {tickrate}");
|
||||
|
||||
var userName = _net.ServerChannel!.UserName;
|
||||
var userId = _net.ServerChannel.UserId;
|
||||
_discord.Update(info.ServerName, userName, info.ServerMaxPlayers.ToString());
|
||||
// start up player management
|
||||
_playMan.Startup(_net.ServerChannel!);
|
||||
|
||||
_playMan.LocalPlayer!.UserId = userId;
|
||||
_playMan.LocalPlayer.Name = userName;
|
||||
|
||||
_playMan.LocalPlayer.StatusChanged += OnLocalStatusChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -152,6 +185,7 @@ namespace Robust.Client
|
||||
|
||||
LastDisconnectReason = args.Reason;
|
||||
|
||||
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
|
||||
_gameStates.Reset();
|
||||
_playMan.Shutdown();
|
||||
_entityManager.Shutdown();
|
||||
@@ -160,36 +194,6 @@ namespace Robust.Client
|
||||
Reset();
|
||||
}
|
||||
|
||||
private void HandleServerInfo(MsgServerInfo msg)
|
||||
{
|
||||
var info = GameInfo;
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
GameInfo = info = new ServerInfo(msg.ServerName);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.ServerName = msg.ServerName;
|
||||
}
|
||||
|
||||
info.ServerMaxPlayers = msg.ServerMaxPlayers;
|
||||
info.TickRate = msg.TickRate;
|
||||
_timing.TickRate = msg.TickRate;
|
||||
Logger.InfoS("client", $"Tickrate changed to: {msg.TickRate}");
|
||||
|
||||
var userName = msg.MsgChannel.UserName;
|
||||
var userId = msg.MsgChannel.UserId;
|
||||
_discord.Update(info.ServerName, userName, info.ServerMaxPlayers.ToString());
|
||||
// start up player management
|
||||
_playMan.Startup(_net.ServerChannel!);
|
||||
|
||||
_playMan.LocalPlayer!.UserId = userId;
|
||||
_playMan.LocalPlayer.Name = userName;
|
||||
|
||||
_playMan.LocalPlayer.StatusChanged += OnLocalStatusChanged;
|
||||
}
|
||||
|
||||
private void HandleSetTickRate(MsgSetTickRate message)
|
||||
{
|
||||
_timing.TickRate = message.NewTickRate;
|
||||
|
||||
@@ -115,6 +115,7 @@ namespace Robust.Client
|
||||
IoCManager.Register<IViewVariablesManagerInternal, ViewVariablesManager>();
|
||||
IoCManager.Register<IClientConGroupController, ClientConGroupController>();
|
||||
IoCManager.Register<IScriptClient, ScriptClient>();
|
||||
//IoCManager.Register<IXamlCompiler, XamlCompiler>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
72
Robust.Client/Console/Commands/AddCompCommand.cs
Normal file
72
Robust.Client/Console/Commands/AddCompCommand.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class AddCompCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "addcompc";
|
||||
public string Description => "Adds a component to an entity on the client";
|
||||
public string Help => "addcompc <uid> <componentName>";
|
||||
|
||||
public bool Execute(IDebugConsole shell, params string[] args)
|
||||
{
|
||||
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.AddLine("Wrong number of arguments");
|
||||
return false;
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
var entity = entityManager.GetEntity(entityUid);
|
||||
var component = (Component) compFactory.GetComponent(componentName);
|
||||
|
||||
component.Owner = entity;
|
||||
|
||||
compManager.AddComponent(entity, component);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
internal sealed class RemoveCompCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "rmcompc";
|
||||
public string Description => "Removes a component from an entity.";
|
||||
public string Help => "rmcompc <uid> <componentName>";
|
||||
|
||||
public bool Execute(IDebugConsole shell, string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.AddLine("Wrong number of arguments");
|
||||
return false;
|
||||
}
|
||||
|
||||
var entityUid = EntityUid.Parse(args[0]);
|
||||
var componentName = args[1];
|
||||
|
||||
var compManager = IoCManager.Resolve<IComponentManager>();
|
||||
var compFactory = IoCManager.Resolve<IComponentFactory>();
|
||||
|
||||
var registration = compFactory.GetRegistration(componentName);
|
||||
|
||||
compManager.RemoveComponent(entityUid, registration.Type);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -781,6 +781,21 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
internal class GcFullCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "gcf";
|
||||
public string Description => "Run the GC, fully, compacting LOH and everything.";
|
||||
public string Help => "gcf";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
{
|
||||
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
|
||||
GC.Collect(2, GCCollectionMode.Forced, true, true);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal class GcModeCommand : IConsoleCommand
|
||||
{
|
||||
|
||||
|
||||
69
Robust.Client/Console/Commands/LauncherAuthCommand.cs
Normal file
69
Robust.Client/Console/Commands/LauncherAuthCommand.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
#if !FULL_RELEASE
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
internal sealed class LauncherAuthCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "launchauth";
|
||||
public string Description => "Load authentication tokens from launcher data to aid in testing of live servers";
|
||||
public string Help => "launchauth [account name]";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
{
|
||||
var wantName = args.Length > 0 ? args[0] : null;
|
||||
|
||||
var basePath = Path.GetDirectoryName(UserDataDir.GetUserDataDir())!;
|
||||
var cfgPath = Path.Combine(basePath, "launcher", "launcher_config.json");
|
||||
|
||||
var data = JsonSerializer.Deserialize<LauncherConfig>(File.ReadAllText(cfgPath))!;
|
||||
|
||||
var login = wantName != null
|
||||
? data.Logins.FirstOrDefault(p => p.Username == wantName)
|
||||
: data.Logins.FirstOrDefault();
|
||||
|
||||
if (login == null)
|
||||
{
|
||||
console.AddLine("Unable to find a matching login");
|
||||
return false;
|
||||
}
|
||||
|
||||
var token = login.Token.Token;
|
||||
var userId = login.UserId;
|
||||
|
||||
var cfg = IoCManager.Resolve<IConfigurationManagerInternal>();
|
||||
cfg.SetSecureCVar(CVars.AuthUserId, userId);
|
||||
cfg.SetSecureCVar(CVars.AuthToken, token);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private sealed class LauncherConfig
|
||||
{
|
||||
[JsonInclude] [JsonPropertyName("logins")]
|
||||
public LauncherLogin[] Logins = default!;
|
||||
}
|
||||
|
||||
private sealed class LauncherLogin
|
||||
{
|
||||
[JsonInclude] public string Username = default!;
|
||||
[JsonInclude] public string UserId = default!;
|
||||
[JsonInclude] public LauncherToken Token = default!;
|
||||
}
|
||||
|
||||
private sealed class LauncherToken
|
||||
{
|
||||
[JsonInclude] public string Token = default!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
35
Robust.Client/Console/Commands/SetInputContextCommand.cs
Normal file
35
Robust.Client/Console/Commands/SetInputContextCommand.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Client.Interfaces.Input;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class SetInputContextCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "setinputcontext";
|
||||
public string Description => "Sets the active input context.";
|
||||
public string Help => "setinputcontext <context>";
|
||||
|
||||
public bool Execute(IDebugConsole console, params string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
console.AddLine("Invalid number of arguments!");
|
||||
return false;
|
||||
}
|
||||
|
||||
var inputMan = IoCManager.Resolve<IInputManager>();
|
||||
|
||||
if (!inputMan.Contexts.Exists(args[0]))
|
||||
{
|
||||
console.AddLine("Context not found!");
|
||||
return false;
|
||||
}
|
||||
|
||||
inputMan.Contexts.SetActiveContext(args[0]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
@@ -164,6 +164,7 @@ namespace Robust.Client
|
||||
|
||||
_userInterfaceManager.Initialize();
|
||||
_networkManager.Initialize(false);
|
||||
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
|
||||
_serializer.Initialize();
|
||||
_inputManager.Initialize();
|
||||
_console.Initialize();
|
||||
|
||||
@@ -48,11 +48,21 @@ namespace Robust.Client.GameObjects.Components.Animations
|
||||
return;
|
||||
}
|
||||
|
||||
List<string>? toRemove = null;
|
||||
// TODO: Get rid of this ToArray() allocation.
|
||||
foreach (var (key, playback) in _playingAnimations.ToArray())
|
||||
{
|
||||
var keep = UpdatePlayback(Owner, playback, frameTime);
|
||||
if (!keep)
|
||||
{
|
||||
toRemove ??= new List<string>();
|
||||
toRemove.Add(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != null)
|
||||
{
|
||||
foreach (var key in toRemove)
|
||||
{
|
||||
_playingAnimations.Remove(key);
|
||||
AnimationCompleted?.Invoke(key);
|
||||
|
||||
@@ -54,17 +54,17 @@ namespace Robust.Client.GameObjects
|
||||
return (T) data[key];
|
||||
}
|
||||
|
||||
public override bool TryGetData<T>(Enum key, [MaybeNullWhen(false)] out T data)
|
||||
public override bool TryGetData<T>(Enum key, [NotNullWhen(true)] out T data)
|
||||
{
|
||||
return TryGetData(key, out data);
|
||||
}
|
||||
|
||||
public override bool TryGetData<T>(string key, [MaybeNullWhen(false)] out T data)
|
||||
public override bool TryGetData<T>(string key, [NotNullWhen(true)] out T data)
|
||||
{
|
||||
return TryGetData(key, out data);
|
||||
}
|
||||
|
||||
internal bool TryGetData<T>(object key, [MaybeNullWhen(false)] out T data)
|
||||
internal bool TryGetData<T>(object key, [NotNullWhen(true)] out T data)
|
||||
{
|
||||
if (this.data.TryGetValue(key, out var dat))
|
||||
{
|
||||
@@ -72,7 +72,7 @@ namespace Robust.Client.GameObjects
|
||||
return true;
|
||||
}
|
||||
|
||||
data = default;
|
||||
data = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,18 @@ namespace Robust.Client.GameObjects
|
||||
set => _energy = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Soft shadow strength multiplier.
|
||||
/// Has no effect if soft shadows are not enabled.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[Animatable]
|
||||
public float Softness
|
||||
{
|
||||
get => _softness;
|
||||
set => _softness = value;
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool VisibleNested
|
||||
{
|
||||
@@ -115,6 +127,7 @@ namespace Robust.Client.GameObjects
|
||||
private bool _maskAutoRotate;
|
||||
private Angle _rotation;
|
||||
private float _energy;
|
||||
private float _softness;
|
||||
|
||||
/// <summary>
|
||||
/// Radius, in meters.
|
||||
@@ -167,6 +180,7 @@ namespace Robust.Client.GameObjects
|
||||
serializer.DataFieldCached(ref _color, "color", Color.White);
|
||||
serializer.DataFieldCached(ref _enabled, "enabled", true);
|
||||
serializer.DataFieldCached(ref _energy, "energy", 1f);
|
||||
serializer.DataFieldCached(ref _softness, "softness", 1f);
|
||||
serializer.DataFieldCached(ref _maskAutoRotate, "autoRot", false);
|
||||
serializer.DataFieldCached(ref _visibleNested, "nestedvisible", true);
|
||||
|
||||
|
||||
@@ -991,6 +991,9 @@ namespace Robust.Client.GameObjects
|
||||
var mRotation = Matrix3.CreateRotation(angle);
|
||||
Matrix3.Multiply(ref mRotation, ref mOffset, out var transform);
|
||||
|
||||
// Only apply scale if needed.
|
||||
if(!Scale.EqualsApprox(Vector2.One)) transform.Multiply(Matrix3.CreateScale(Scale));
|
||||
|
||||
transform.Multiply(worldTransform);
|
||||
|
||||
RenderInternal(drawingHandle, worldRotation, overrideDirection, transform);
|
||||
|
||||
@@ -128,7 +128,8 @@ namespace Robust.Client.GameObjects.EntitySystems
|
||||
|
||||
if (!entity.TryGetComponent(out InputComponent? inputComp))
|
||||
{
|
||||
Logger.DebugS("input.context", $"AttachedEnt has no InputComponent: entId={entity.Uid}, entProto={entity.Prototype}");
|
||||
Logger.DebugS("input.context", $"AttachedEnt has no InputComponent: entId={entity.Uid}, entProto={entity.Prototype}. Setting default \"{InputContextContainer.DefaultContextName}\" context...");
|
||||
inputMan.Contexts.SetActiveContext(InputContextContainer.DefaultContextName);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -138,7 +139,8 @@ namespace Robust.Client.GameObjects.EntitySystems
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.ErrorS("input.context", $"Unknown context: entId={entity.Uid}, entProto={entity.Prototype}, context={inputComp.ContextName}");
|
||||
Logger.ErrorS("input.context", $"Unknown context: entId={entity.Uid}, entProto={entity.Prototype}, context={inputComp.ContextName}. . Setting default \"{InputContextContainer.DefaultContextName}\" context...");
|
||||
inputMan.Contexts.SetActiveContext(InputContextContainer.DefaultContextName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Robust.Client.GameObjects.EntitySystems
|
||||
/// Handles interpolation of transform positions.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
internal sealed class TransformSystem : EntitySystem
|
||||
internal sealed class TransformSystem : SharedTransformSystem
|
||||
{
|
||||
// Max distance per tick how far an entity can move before it is considered teleporting.
|
||||
// TODO: Make these values somehow dependent on server TPS.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.GameObjects.EntitySystems;
|
||||
using Robust.Client.Interfaces;
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
@@ -41,7 +42,7 @@ namespace Robust.Client.GameStates
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IComponentManager _componentManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
@@ -57,7 +58,8 @@ namespace Robust.Client.GameStates
|
||||
|
||||
public bool Predicting { get; private set; }
|
||||
|
||||
public int PredictSize { get; private set; }
|
||||
public int PredictTickBias { get; private set; }
|
||||
public float PredictLagBias { get; private set; }
|
||||
|
||||
public int StateBufferMergeThreshold { get; private set; }
|
||||
|
||||
@@ -82,14 +84,16 @@ namespace Robust.Client.GameStates
|
||||
_config.OnValueChanged(CVars.NetInterpRatio, i => _processor.InterpRatio = i, true);
|
||||
_config.OnValueChanged(CVars.NetLogging, b => _processor.Logging = b, true);
|
||||
_config.OnValueChanged(CVars.NetPredict, b => Predicting = b, true);
|
||||
_config.OnValueChanged(CVars.NetPredictSize, i => PredictSize = i, true);
|
||||
_config.OnValueChanged(CVars.NetPredictTickBias, i => PredictTickBias = i, true);
|
||||
_config.OnValueChanged(CVars.NetPredictLagBias, i => PredictLagBias = i, true);
|
||||
_config.OnValueChanged(CVars.NetStateBufMergeThreshold, i => StateBufferMergeThreshold = i, true);
|
||||
|
||||
_processor.Interpolation = _config.GetCVar(CVars.NetInterp);
|
||||
_processor.InterpRatio = _config.GetCVar(CVars.NetInterpRatio);
|
||||
_processor.Logging = _config.GetCVar(CVars.NetLogging);
|
||||
Predicting = _config.GetCVar(CVars.NetPredict);
|
||||
PredictSize = _config.GetCVar(CVars.NetPredictSize);
|
||||
PredictTickBias = _config.GetCVar(CVars.NetPredictTickBias);
|
||||
PredictLagBias = _config.GetCVar(CVars.NetPredictLagBias);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -174,7 +178,7 @@ namespace Robust.Client.GameStates
|
||||
var i = 0;
|
||||
for (; i < applyCount; i++)
|
||||
{
|
||||
_timing.CurTick = _lastProcessedTick + 1;
|
||||
_timing.LastRealTick = _timing.CurTick = _lastProcessedTick + 1;
|
||||
|
||||
// TODO: We could theoretically communicate with the GameStateProcessor better here.
|
||||
// Since game states are sliding windows, it is possible that we need less than applyCount applies here.
|
||||
@@ -256,9 +260,9 @@ namespace Robust.Client.GameStates
|
||||
var hasPendingInput = pendingInputEnumerator.MoveNext();
|
||||
var hasPendingMessage = pendingMessagesEnumerator.MoveNext();
|
||||
|
||||
var ping = _network.ServerChannel!.Ping / 1000f; // seconds.
|
||||
var ping = _network.ServerChannel!.Ping / 1000f + PredictLagBias; // seconds.
|
||||
var targetTick = _timing.CurTick.Value + _processor.TargetBufferSize +
|
||||
(int) Math.Ceiling(_timing.TickRate * ping) + PredictSize;
|
||||
(int) Math.Ceiling(_timing.TickRate * ping) + PredictTickBias;
|
||||
|
||||
// Logger.DebugS("net.predict", $"Predicting from {_lastProcessedTick} to {targetTick}");
|
||||
|
||||
@@ -377,6 +381,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private List<EntityUid> ApplyGameState(GameState curState, GameState? nextState)
|
||||
{
|
||||
_config.TickProcessMessages();
|
||||
_mapManager.ApplyGameStatePre(curState.MapData);
|
||||
var createdEntities = _entities.ApplyEntityStates(curState.EntityStates, curState.EntityDeletions,
|
||||
nextState?.EntityStates);
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
while (readSamples < totalSamples)
|
||||
{
|
||||
var read = vorbis.ReadSamples(buffer, readSamples * channels, (int)totalSamples - readSamples);
|
||||
var read = vorbis.ReadSamples(buffer, readSamples * channels, buffer.Length - readSamples);
|
||||
if (read == 0)
|
||||
{
|
||||
break;
|
||||
|
||||
@@ -11,6 +11,22 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
static Clyde()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
|
||||
RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
||||
{
|
||||
try
|
||||
{
|
||||
// We force load nvapi64.dll so nvidia gives us the dedicated GPU on optimus laptops.
|
||||
// This is 100x easier than nvidia's documented approach of NvOptimusEnablement,
|
||||
// and works while developing.
|
||||
NativeLibrary.Load("nvapi64.dll");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// If this fails whatever.
|
||||
}
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -377,6 +377,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var lastRange = float.NaN;
|
||||
var lastPower = float.NaN;
|
||||
var lastColor = new Color(float.NaN, float.NaN, float.NaN, float.NaN);
|
||||
var lastSoftness = float.NaN;
|
||||
Texture? lastMask = null;
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
@@ -424,6 +425,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
lightShader.SetUniformMaybe("lightColor", lastColor);
|
||||
}
|
||||
|
||||
if (_enableSoftShadows && !MathHelper.CloseTo(lastSoftness, component.Softness))
|
||||
{
|
||||
lastSoftness = component.Softness;
|
||||
lightShader.SetUniformMaybe("lightSoftness", lastSoftness);
|
||||
}
|
||||
|
||||
lightShader.SetUniformMaybe("lightCenter", lightPos);
|
||||
lightShader.SetUniformMaybe("lightIndex", (i + 0.5f) / ShadowTexture.Height);
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
CheckGlError();
|
||||
|
||||
// Check on original format is NOT a bug, this is so srgb emulation works
|
||||
textureObject = GenTexture(texture, size, format.ColorFormat == RTCF.Rgba8Srgb, name == null ? null : $"{name}-color");
|
||||
textureObject = GenTexture(texture, size, format.ColorFormat == RTCF.Rgba8Srgb, name == null ? null : $"{name}-color", TexturePixelType.RenderTarget);
|
||||
}
|
||||
|
||||
// Depth/stencil buffers.
|
||||
|
||||
@@ -11,6 +11,9 @@ using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using OGLTextureWrapMode = OpenToolkit.Graphics.OpenGL.TextureWrapMode;
|
||||
using PIF = OpenToolkit.Graphics.OpenGL4.PixelInternalFormat;
|
||||
using PF = OpenToolkit.Graphics.OpenGL4.PixelFormat;
|
||||
using PT = OpenToolkit.Graphics.OpenGL4.PixelType;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -20,8 +23,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private ClydeTexture _stockTextureBlack = default!;
|
||||
private ClydeTexture _stockTextureTransparent = default!;
|
||||
|
||||
private readonly Dictionary<ClydeHandle, LoadedTexture> _loadedTextures =
|
||||
new();
|
||||
private readonly Dictionary<ClydeHandle, LoadedTexture> _loadedTextures = new();
|
||||
|
||||
private readonly ConcurrentQueue<ClydeHandle> _textureDisposeQueue = new();
|
||||
|
||||
@@ -69,85 +71,129 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Flip image because OpenGL reads images upside down.
|
||||
var copy = FlipClone(image);
|
||||
|
||||
var texture = new GLHandle((uint) GL.GenTexture());
|
||||
CheckGlError();
|
||||
GL.BindTexture(TextureTarget.Texture2D, texture.Handle);
|
||||
CheckGlError();
|
||||
ApplySampleParameters(actualParams.SampleParameters);
|
||||
|
||||
PixelInternalFormat internalFormat;
|
||||
PixelFormat pixelDataFormat;
|
||||
PixelType pixelDataType;
|
||||
bool isActuallySrgb = false;
|
||||
|
||||
if (pixelType == typeof(Rgba32))
|
||||
{
|
||||
// Note that if _hasGLSrgb is off, we import an sRGB texture as non-sRGB.
|
||||
// Shaders are expected to compensate for this
|
||||
internalFormat = (actualParams.Srgb && _hasGLSrgb) ? PixelInternalFormat.Srgb8Alpha8 : PixelInternalFormat.Rgba8;
|
||||
isActuallySrgb = actualParams.Srgb;
|
||||
pixelDataFormat = PixelFormat.Rgba;
|
||||
pixelDataType = PixelType.UnsignedByte;
|
||||
}
|
||||
else if (pixelType == typeof(A8))
|
||||
{
|
||||
if (image.Width % 4 != 0 || image.Height % 4 != 0)
|
||||
{
|
||||
throw new ArgumentException("Alpha8 images must have multiple of 4 sizes.");
|
||||
}
|
||||
|
||||
internalFormat = PixelInternalFormat.R8;
|
||||
pixelDataFormat = PixelFormat.Red;
|
||||
pixelDataType = PixelType.UnsignedByte;
|
||||
|
||||
unsafe
|
||||
{
|
||||
// TODO: Does it make sense to default to 1 for RGB parameters?
|
||||
// It might make more sense to pass some options to change swizzling.
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, (int) All.One);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, (int) All.One);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleB, (int) All.One);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, (int) All.Red);
|
||||
}
|
||||
}
|
||||
else if (pixelType == typeof(L8) && !actualParams.Srgb)
|
||||
{
|
||||
// Can only use R8 for L8 if sRGB is OFF.
|
||||
// Because OpenGL doesn't provide sRGB single/dual channel image formats.
|
||||
// Vulkan when?
|
||||
if (copy.Width % 4 != 0 || copy.Height % 4 != 0)
|
||||
{
|
||||
throw new ArgumentException("L8 non-sRGB images must have multiple of 4 sizes.");
|
||||
}
|
||||
|
||||
internalFormat = PixelInternalFormat.R8;
|
||||
pixelDataFormat = PixelFormat.Red;
|
||||
pixelDataType = PixelType.UnsignedByte;
|
||||
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, (int) All.Red);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, (int) All.Red);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleB, (int) All.Red);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, (int) All.One);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException($"Unable to handle pixel type '{pixelType.Name}'");
|
||||
}
|
||||
var texture = CreateBaseTextureInternal<T>(image.Width, image.Height, actualParams, name);
|
||||
|
||||
unsafe
|
||||
{
|
||||
var span = copy.GetPixelSpan();
|
||||
fixed (T* ptr = span)
|
||||
{
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, internalFormat, copy.Width, copy.Height, 0,
|
||||
pixelDataFormat, pixelDataType, (IntPtr) ptr);
|
||||
CheckGlError();
|
||||
// Still bound.
|
||||
DoTexUpload(copy.Width, copy.Height, actualParams.Srgb, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
var pressureEst = EstPixelSize(internalFormat) * copy.Width * copy.Height;
|
||||
return texture;
|
||||
}
|
||||
|
||||
return GenTexture(texture, (copy.Width, copy.Height), isActuallySrgb, name, pressureEst);
|
||||
public unsafe OwnedTexture CreateBlankTexture<T>(
|
||||
Vector2i size,
|
||||
string? name = null,
|
||||
in TextureLoadParameters? loadParams = null)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
var actualParams = loadParams ?? TextureLoadParameters.Default;
|
||||
if (!_hasGLTextureSwizzle)
|
||||
{
|
||||
// Actually create RGBA32 texture if missing texture swizzle.
|
||||
// This is fine (TexturePixelType that's stored) because all other APIs do the same.
|
||||
if (typeof(T) == typeof(A8) || typeof(T) == typeof(L8))
|
||||
{
|
||||
return CreateBlankTexture<Rgba32>(size, name, loadParams);
|
||||
}
|
||||
}
|
||||
|
||||
var texture = CreateBaseTextureInternal<T>(
|
||||
size.X, size.Y,
|
||||
actualParams,
|
||||
name);
|
||||
|
||||
// Texture still bound, run glTexImage2D with null data param to specify bounds.
|
||||
DoTexUpload<T>(size.X, size.Y, actualParams.Srgb, null);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
private unsafe void DoTexUpload<T>(int width, int height, bool srgb, T* ptr) where T : unmanaged, IPixel<T>
|
||||
{
|
||||
if (sizeof(T) < 4)
|
||||
{
|
||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
var (pif, pf, pt) = PixelEnums<T>(srgb);
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, pif, width, height, 0, pf, pt, (IntPtr) ptr);
|
||||
CheckGlError();
|
||||
|
||||
if (sizeof(T) < 4)
|
||||
{
|
||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);
|
||||
CheckGlError();
|
||||
}
|
||||
}
|
||||
|
||||
private ClydeTexture CreateBaseTextureInternal<T>(
|
||||
int width, int height,
|
||||
in TextureLoadParameters loadParams,
|
||||
string? name = null)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
var texture = new GLHandle((uint) GL.GenTexture());
|
||||
CheckGlError();
|
||||
GL.BindTexture(TextureTarget.Texture2D, texture.Handle);
|
||||
CheckGlError();
|
||||
ApplySampleParameters(loadParams.SampleParameters);
|
||||
|
||||
var (pif, _, _) = PixelEnums<T>(loadParams.Srgb);
|
||||
var pixelType = typeof(T);
|
||||
var texPixType = GetTexturePixelType<T>();
|
||||
var isActuallySrgb = false;
|
||||
|
||||
if (pixelType == typeof(Rgba32))
|
||||
{
|
||||
isActuallySrgb = loadParams.Srgb;
|
||||
}
|
||||
else if (pixelType == typeof(A8))
|
||||
{
|
||||
DebugTools.Assert(_hasGLTextureSwizzle);
|
||||
|
||||
// TODO: Does it make sense to default to 1 for RGB parameters?
|
||||
// It might make more sense to pass some options to change swizzling.
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, (int) All.One);
|
||||
CheckGlError();
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, (int) All.One);
|
||||
CheckGlError();
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleB, (int) All.One);
|
||||
CheckGlError();
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, (int) All.Red);
|
||||
CheckGlError();
|
||||
}
|
||||
else if (pixelType == typeof(L8) && !loadParams.Srgb)
|
||||
{
|
||||
DebugTools.Assert(_hasGLTextureSwizzle);
|
||||
|
||||
// Can only use R8 for L8 if sRGB is OFF.
|
||||
// Because OpenGL doesn't provide sRGB single/dual channel image formats.
|
||||
// Vulkan when?
|
||||
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, (int) All.Red);
|
||||
CheckGlError();
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, (int) All.Red);
|
||||
CheckGlError();
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleB, (int) All.Red);
|
||||
CheckGlError();
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, (int) All.One);
|
||||
CheckGlError();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Unable to handle pixel type '{pixelType.Name}'");
|
||||
}
|
||||
|
||||
var pressureEst = EstPixelSize(pif) * width * height;
|
||||
|
||||
return GenTexture(texture, (width, height), isActuallySrgb, name, texPixType, pressureEst);
|
||||
}
|
||||
|
||||
private void ApplySampleParameters(TextureSampleParameters? sampleParameters)
|
||||
@@ -201,10 +247,30 @@ namespace Robust.Client.Graphics.Clyde
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
private ClydeTexture GenTexture(GLHandle glHandle, Vector2i size, bool srgb, string? name, long memoryPressure=0)
|
||||
private (PIF pif, PF pf, PT pt) PixelEnums<T>(bool srgb)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
return default(T) switch
|
||||
{
|
||||
// Note that if _hasGLSrgb is off, we import an sRGB texture as non-sRGB.
|
||||
// Shaders are expected to compensate for this
|
||||
Rgba32 => (srgb && _hasGLSrgb ? PIF.Srgb8Alpha8 : PIF.Rgba8, PF.Rgba, PT.UnsignedByte),
|
||||
A8 or L8 => (PIF.R8, PF.Red, PT.UnsignedByte),
|
||||
_ => throw new NotSupportedException("Unsupported pixel type."),
|
||||
};
|
||||
}
|
||||
|
||||
private ClydeTexture GenTexture(
|
||||
GLHandle glHandle,
|
||||
Vector2i size,
|
||||
bool srgb,
|
||||
string? name,
|
||||
TexturePixelType pixType,
|
||||
long memoryPressure = 0)
|
||||
{
|
||||
if (name != null)
|
||||
{
|
||||
@@ -222,7 +288,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
Height = height,
|
||||
IsSrgb = srgb,
|
||||
Name = name,
|
||||
MemoryPressure = memoryPressure
|
||||
MemoryPressure = memoryPressure,
|
||||
TexturePixelType = pixType
|
||||
// TextureInstance = new WeakReference<ClydeTexture>(instance)
|
||||
};
|
||||
|
||||
@@ -246,6 +313,97 @@ namespace Robust.Client.Graphics.Clyde
|
||||
//GC.RemoveMemoryPressure(loadedTexture.MemoryPressure);
|
||||
}
|
||||
|
||||
private unsafe void SetSubImage<T>(
|
||||
ClydeTexture texture,
|
||||
Vector2i dstTl,
|
||||
Image<T> srcImage,
|
||||
in UIBox2i srcBox)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
if (!_hasGLTextureSwizzle)
|
||||
{
|
||||
if (typeof(T) == typeof(A8))
|
||||
{
|
||||
SetSubImage(texture, dstTl, ApplyA8Swizzle((Image<A8>) (object) srcImage), srcBox);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(L8))
|
||||
{
|
||||
SetSubImage(texture, dstTl, ApplyL8Swizzle((Image<L8>) (object) srcImage), srcBox);
|
||||
}
|
||||
}
|
||||
|
||||
var loaded = _loadedTextures[texture.TextureId];
|
||||
|
||||
var pixType = GetTexturePixelType<T>();
|
||||
|
||||
if (pixType != loaded.TexturePixelType)
|
||||
{
|
||||
if (loaded.TexturePixelType == TexturePixelType.RenderTarget)
|
||||
throw new InvalidOperationException("Cannot modify texture for render target directly.");
|
||||
|
||||
throw new InvalidOperationException("Mismatching pixel type for texture.");
|
||||
}
|
||||
|
||||
if (loaded.Width < dstTl.X + srcBox.Width || loaded.Height < dstTl.Y + srcBox.Height)
|
||||
throw new ArgumentOutOfRangeException(nameof(srcBox), "Destination rectangle out of bounds.");
|
||||
|
||||
if (srcBox.Left < 0 || srcBox.Top < 0 || srcBox.Right > srcImage.Width || srcBox.Bottom > srcImage.Height)
|
||||
throw new ArgumentOutOfRangeException(nameof(srcBox), "Source rectangle out of bounds.");
|
||||
|
||||
if (sizeof(T) != 4)
|
||||
{
|
||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
// sRGB doesn't matter since that only changes the internalFormat, which we don't need here.
|
||||
var (_, pf, pt) = PixelEnums<T>(srgb: false);
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, loaded.OpenGLObject.Handle);
|
||||
CheckGlError();
|
||||
|
||||
var size = srcBox.Width * srcBox.Height;
|
||||
|
||||
var copyBuffer = size < 16 * 16 ? stackalloc T[size] : new T[size];
|
||||
|
||||
for (var y = 0; y < srcBox.Height; y++)
|
||||
for (var x = 0; x < srcBox.Width; x++)
|
||||
{
|
||||
copyBuffer[(srcBox.Height - y - 1) * srcBox.Width + x] = srcImage[x + srcBox.Left, srcBox.Top + y];
|
||||
}
|
||||
|
||||
fixed (T* aPtr = copyBuffer)
|
||||
{
|
||||
var dstY = loaded.Height - dstTl.Y - srcBox.Height;
|
||||
GL.TexSubImage2D(
|
||||
TextureTarget.Texture2D,
|
||||
0,
|
||||
dstTl.X, dstY,
|
||||
srcBox.Width, srcBox.Height,
|
||||
pf, pt,
|
||||
(IntPtr) aPtr);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
if (sizeof(T) != 4)
|
||||
{
|
||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);
|
||||
CheckGlError();
|
||||
}
|
||||
}
|
||||
|
||||
private static TexturePixelType GetTexturePixelType<T>() where T : unmanaged, IPixel<T>
|
||||
{
|
||||
return default(T) switch
|
||||
{
|
||||
Rgba32 => TexturePixelType.Rgba32,
|
||||
L8 => TexturePixelType.L8,
|
||||
A8 => TexturePixelType.A8,
|
||||
_ => throw new NotSupportedException("Unsupported pixel type."),
|
||||
};
|
||||
}
|
||||
|
||||
private void LoadStockTextures()
|
||||
{
|
||||
var white = new Image<Rgba32>(1, 1);
|
||||
@@ -350,10 +508,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public bool IsSrgb;
|
||||
public string? Name;
|
||||
public long MemoryPressure;
|
||||
public TexturePixelType TexturePixelType;
|
||||
|
||||
public Vector2i Size => (Width, Height);
|
||||
// public WeakReference<ClydeTexture> TextureInstance;
|
||||
}
|
||||
|
||||
private enum TexturePixelType : byte
|
||||
{
|
||||
RenderTarget = 0,
|
||||
Rgba32,
|
||||
A8,
|
||||
L8,
|
||||
}
|
||||
|
||||
private void FlushTextureDispose()
|
||||
{
|
||||
while (_textureDisposeQueue.TryDequeue(out var handle))
|
||||
@@ -369,6 +537,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
internal ClydeHandle TextureId { get; }
|
||||
|
||||
public override void SetSubImage<T>(Vector2i topLeft, Image<T> sourceImage, in UIBox2i sourceRegion)
|
||||
{
|
||||
_clyde.SetSubImage(this, topLeft, sourceImage, sourceRegion);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public string GetKeyName(Keyboard.Key key) => string.Empty;
|
||||
public string GetKeyNameScanCode(int scanCode) => string.Empty;
|
||||
public int GetKeyScanCode(Keyboard.Key key) => default;
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
// Nada.
|
||||
@@ -77,8 +78,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public override event Action<WindowResizedEventArgs> OnWindowResized
|
||||
{
|
||||
add {}
|
||||
remove {}
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public void Render()
|
||||
@@ -111,6 +112,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return new DummyTexture((image.Width, image.Height));
|
||||
}
|
||||
|
||||
public OwnedTexture CreateBlankTexture<T>(
|
||||
Vector2i size,
|
||||
string? name = null,
|
||||
in TextureLoadParameters? loadParams = null)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
return new DummyTexture(size);
|
||||
}
|
||||
|
||||
public IRenderTexture CreateRenderTarget(Vector2i size, RenderTargetFormatParameters format,
|
||||
TextureSampleParameters? sampleParameters = null, string? name = null)
|
||||
{
|
||||
@@ -174,7 +184,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return DummyAudioSource.Instance;
|
||||
}
|
||||
|
||||
public IClydeBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false)
|
||||
public IClydeBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio = false)
|
||||
{
|
||||
return DummyBufferedAudioSource.Instance;
|
||||
}
|
||||
@@ -296,6 +306,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public DummyTexture(Vector2i size) : base(size)
|
||||
{
|
||||
}
|
||||
|
||||
public override void SetSubImage<T>(Vector2i topLeft, Image<T> sourceImage, in UIBox2i sourceRegion)
|
||||
{
|
||||
// Just do nothing on mutate.
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DummyShaderInstance : ShaderInstance
|
||||
@@ -434,7 +449,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
}
|
||||
|
||||
public IRenderTexture RenderTarget { get; } = new DummyRenderTexture(Vector2i.One, new DummyTexture(Vector2i.One));
|
||||
public IRenderTexture RenderTarget { get; } =
|
||||
new DummyRenderTexture(Vector2i.One, new DummyTexture(Vector2i.One));
|
||||
|
||||
public IEye? Eye { get; set; }
|
||||
public Vector2i Size { get; }
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
|
||||
// xy: A, zw: B
|
||||
varying highp vec4 fragPos;
|
||||
// x: actual angle, y: horizontal (1) / vertical (-1)
|
||||
varying highp vec2 fragAngle;
|
||||
// x: Angle being queried, y: Angle of closest point of line (is of 90-degree angle to line angle), z: Distance at y
|
||||
varying highp vec3 fragControl;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Stuff that needs to be inferred to avoid interpolation issues.
|
||||
highp vec2 rayNormal = vec2(cos(fragAngle.x), -sin(fragAngle.x));
|
||||
|
||||
// Depth calculation accounting for interpolation.
|
||||
highp float dist;
|
||||
|
||||
if (fragAngle.y > 0.0) {
|
||||
// Line is horizontal
|
||||
dist = abs(fragPos.y / rayNormal.y);
|
||||
} else {
|
||||
// Line is vertical
|
||||
dist = abs(fragPos.x / rayNormal.x);
|
||||
}
|
||||
// Thanks to Radrark for finding this for me. There's also a useful diagram, but this is text, so:
|
||||
// r = p / cos(theta - phi)
|
||||
// r: Distance to line *given angle theta*
|
||||
// p: Distance to closest point of line
|
||||
// theta: Angle being queried
|
||||
// phi: Angle of closest point of line - inherently on 90-degree angle to line angle
|
||||
highp float dist = abs(fragControl.z / cos(fragControl.x - fragControl.y));
|
||||
|
||||
// Main body.
|
||||
#ifdef HAS_DFDX
|
||||
|
||||
@@ -11,10 +11,8 @@ attribute vec4 aPos;
|
||||
// x: deflection(0=A/1=B) y: height
|
||||
attribute vec2 subVertex;
|
||||
|
||||
// xy: A, zw: B
|
||||
varying vec4 fragPos;
|
||||
// x: actual angle, y: horizontal (1) / vertical (-1)
|
||||
varying vec2 fragAngle;
|
||||
// x: actual angle, y: line angle + 90 degrees, z: Distance at y
|
||||
varying vec3 fragControl;
|
||||
|
||||
// Note: This is *not* the standard projectionMatrix!
|
||||
uniform vec2 shadowLightCentre;
|
||||
@@ -70,8 +68,19 @@ void main()
|
||||
}
|
||||
}
|
||||
|
||||
fragPos = vec4(pA, pB);
|
||||
fragAngle = vec2(mix(xA, xB, subVertex.x), abs(pA.x - pB.x) - abs(pA.y - pB.y));
|
||||
float targetAngle = mix(xA, xB, subVertex.x);
|
||||
|
||||
// Calculate the necessary control data for the fragment shader.
|
||||
vec2 lineNormal = pB - pA; // hypothetical: <- would have negative X, zero Y
|
||||
lineNormal /= length(lineNormal);
|
||||
fragControl = vec3(
|
||||
// Angle
|
||||
targetAngle,
|
||||
// Angle Out
|
||||
atan(lineNormal.x, lineNormal.y),
|
||||
// Distance @ Angle Out
|
||||
dot(vec2(lineNormal.y, -lineNormal.x), pA)
|
||||
);
|
||||
|
||||
// Depth divide MUST be implemented here no matter what,
|
||||
// because GLES SL 1.00 doesn't have gl_FragDepth.
|
||||
@@ -79,5 +88,5 @@ void main()
|
||||
// and we don't really need to have correction
|
||||
float zbufferDepth = 1.0 - (1.0 / (length(mix(pA, pB, subVertex.x)) + DEPTH_ZBUFFER_PREDIV_BIAS));
|
||||
|
||||
gl_Position = vec4(mix(xA, xB, subVertex.x) / PI, mix(1.0, -1.0, subVertex.y), zbufferDepth, 1.0);
|
||||
gl_Position = vec4(targetAngle / PI, mix(1.0, -1.0, subVertex.y), zbufferDepth, 1.0);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Interfaces.Graphics;
|
||||
using Robust.Client.Utility;
|
||||
@@ -17,7 +18,11 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
internal sealed class FontManager : IFontManagerInternal
|
||||
{
|
||||
private const int SheetWidth = 256;
|
||||
private const int SheetHeight = 256;
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
|
||||
private uint BaseFontDPI;
|
||||
|
||||
@@ -51,8 +56,7 @@ namespace Robust.Client.Graphics
|
||||
return instance;
|
||||
}
|
||||
|
||||
var glyphMap = _generateGlyphMap(fontFaceHandle.Face);
|
||||
instance = new FontInstanceHandle(this, size, glyphMap, fontFaceHandle);
|
||||
instance = new FontInstanceHandle(this, size, fontFaceHandle);
|
||||
|
||||
_loadedInstances.Add((fontFaceHandle, size), instance);
|
||||
return instance;
|
||||
@@ -67,135 +71,128 @@ namespace Robust.Client.Graphics
|
||||
var descent = -ftFace.Size.Metrics.Descender.ToInt32();
|
||||
var lineHeight = ftFace.Size.Metrics.Height.ToInt32();
|
||||
|
||||
var (atlas, metricsMap) = _generateAtlas(instance, scale);
|
||||
var data = new ScaledFontData(ascent, descent, ascent + descent, lineHeight);
|
||||
|
||||
return new ScaledFontData(metricsMap, ascent, descent, ascent + descent, lineHeight, atlas);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private (FontTextureAtlas, Dictionary<uint, CharMetrics> metricsMap)
|
||||
_generateAtlas(FontInstanceHandle instance, float scale)
|
||||
private void CacheGlyph(FontInstanceHandle instance, ScaledFontData scaled, float scale, uint glyph)
|
||||
{
|
||||
// TODO: This could use a better box packing algorithm.
|
||||
// Right now we treat each glyph bitmap as having the max size among all glyphs.
|
||||
// So we can divide the atlas into equal-size rectangles.
|
||||
// This wastes a lot of space though because there's a lot of tiny glyphs.
|
||||
// Check if already cached.
|
||||
if (scaled.AtlasData.ContainsKey(glyph))
|
||||
return;
|
||||
|
||||
var face = instance.FaceHandle.Face;
|
||||
face.SetCharSize(0, instance.Size, 0, (uint) (BaseFontDPI * scale));
|
||||
face.LoadGlyph(glyph, LoadFlags.Default, LoadTarget.Normal);
|
||||
face.Glyph.RenderGlyph(RenderMode.Normal);
|
||||
|
||||
var maxGlyphSize = Vector2i.Zero;
|
||||
var count = 0;
|
||||
var glyphMetrics = face.Glyph.Metrics;
|
||||
var metrics = new CharMetrics(glyphMetrics.HorizontalBearingX.ToInt32(),
|
||||
glyphMetrics.HorizontalBearingY.ToInt32(),
|
||||
glyphMetrics.HorizontalAdvance.ToInt32(),
|
||||
glyphMetrics.Width.ToInt32(),
|
||||
glyphMetrics.Height.ToInt32());
|
||||
|
||||
var metricsMap = new Dictionary<uint, CharMetrics>();
|
||||
|
||||
foreach (var glyph in instance.GlyphMap.Values)
|
||||
using var bitmap = face.Glyph.Bitmap;
|
||||
if (bitmap.Pitch < 0)
|
||||
{
|
||||
if (metricsMap.ContainsKey(glyph))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
face.LoadGlyph(glyph, LoadFlags.Default, LoadTarget.Normal);
|
||||
face.Glyph.RenderGlyph(RenderMode.Normal);
|
||||
|
||||
var glyphMetrics = face.Glyph.Metrics;
|
||||
var metrics = new CharMetrics(glyphMetrics.HorizontalBearingX.ToInt32(),
|
||||
glyphMetrics.HorizontalBearingY.ToInt32(),
|
||||
glyphMetrics.HorizontalAdvance.ToInt32(),
|
||||
glyphMetrics.Width.ToInt32(),
|
||||
glyphMetrics.Height.ToInt32());
|
||||
metricsMap.Add(glyph, metrics);
|
||||
|
||||
maxGlyphSize = Vector2i.ComponentMax(maxGlyphSize,
|
||||
new Vector2i(face.Glyph.Bitmap.Width, face.Glyph.Bitmap.Rows));
|
||||
|
||||
count += 1;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// Make atlas.
|
||||
// This is the same algorithm used for RSIs. Tries to keep width and height as close as possible,
|
||||
// but preferring to increase width if necessary.
|
||||
var atlasEntriesHorizontal = (int) Math.Ceiling(Math.Sqrt(count));
|
||||
var atlasEntriesVertical =
|
||||
(int) Math.Ceiling(count / (float) atlasEntriesHorizontal);
|
||||
var atlasDimX =
|
||||
(int) Math.Ceiling(atlasEntriesHorizontal * maxGlyphSize.X / 4f) * 4;
|
||||
var atlasDimY =
|
||||
(int) Math.Ceiling(atlasEntriesVertical * maxGlyphSize.Y / 4f) * 4;
|
||||
|
||||
using (var atlas = new Image<A8>(atlasDimX, atlasDimY))
|
||||
if (bitmap.Pitch != 0)
|
||||
{
|
||||
var atlasRegions = new Dictionary<uint, UIBox2>();
|
||||
count = 0;
|
||||
foreach (var glyph in metricsMap.Keys)
|
||||
Image<A8> img;
|
||||
switch (bitmap.PixelMode)
|
||||
{
|
||||
face.LoadGlyph(glyph, LoadFlags.Default, LoadTarget.Normal);
|
||||
face.Glyph.RenderGlyph(RenderMode.Normal);
|
||||
|
||||
var bitmap = face.Glyph.Bitmap;
|
||||
if (bitmap.Pitch == 0)
|
||||
case PixelMode.Mono:
|
||||
{
|
||||
count += 1;
|
||||
continue;
|
||||
img = MonoBitMapToImage(bitmap);
|
||||
break;
|
||||
}
|
||||
|
||||
if (bitmap.Pitch < 0)
|
||||
case PixelMode.Gray:
|
||||
{
|
||||
ReadOnlySpan<A8> span;
|
||||
unsafe
|
||||
{
|
||||
span = new ReadOnlySpan<A8>((void*) bitmap.Buffer, bitmap.Pitch * bitmap.Rows);
|
||||
}
|
||||
|
||||
img = new Image<A8>(bitmap.Width, bitmap.Rows);
|
||||
|
||||
span.Blit(
|
||||
bitmap.Pitch,
|
||||
UIBox2i.FromDimensions(0, 0, bitmap.Pitch, bitmap.Rows),
|
||||
img,
|
||||
(0, 0));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PixelMode.Gray2:
|
||||
case PixelMode.Gray4:
|
||||
case PixelMode.Lcd:
|
||||
case PixelMode.VerticalLcd:
|
||||
case PixelMode.Bgra:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
var column = count % atlasEntriesHorizontal;
|
||||
var row = count / atlasEntriesVertical;
|
||||
var offsetX = column * maxGlyphSize.X;
|
||||
var offsetY = row * maxGlyphSize.Y;
|
||||
count += 1;
|
||||
atlasRegions.Add(glyph, UIBox2i.FromDimensions(offsetX, offsetY, bitmap.Width, bitmap.Rows));
|
||||
|
||||
switch (bitmap.PixelMode)
|
||||
{
|
||||
case PixelMode.Mono:
|
||||
{
|
||||
using (var bitmapImage = MonoBitMapToImage(bitmap))
|
||||
{
|
||||
bitmapImage.Blit(new UIBox2i(0, 0, bitmapImage.Width, bitmapImage.Height), atlas,
|
||||
(offsetX, offsetY));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PixelMode.Gray:
|
||||
{
|
||||
ReadOnlySpan<A8> span;
|
||||
unsafe
|
||||
{
|
||||
span = new ReadOnlySpan<A8>((void*) bitmap.Buffer, bitmap.Pitch * bitmap.Rows);
|
||||
}
|
||||
|
||||
span.Blit(bitmap.Pitch, UIBox2i.FromDimensions(0, 0, bitmap.Pitch, bitmap.Rows), atlas,
|
||||
(offsetX, offsetY));
|
||||
break;
|
||||
}
|
||||
|
||||
case PixelMode.Gray2:
|
||||
case PixelMode.Gray4:
|
||||
case PixelMode.Lcd:
|
||||
case PixelMode.VerticalLcd:
|
||||
case PixelMode.Bgra:
|
||||
throw new NotImplementedException();
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
var atlasDictionary = new Dictionary<uint, AtlasTexture>();
|
||||
var texture = Texture.LoadFromImage(atlas, $"font-{face.FamilyName}-{instance.Size}-{(uint) (BaseFontDPI * scale)}");
|
||||
OwnedTexture sheet;
|
||||
if (scaled.AtlasTextures.Count == 0)
|
||||
sheet = GenSheet();
|
||||
else
|
||||
sheet = scaled.AtlasTextures[^1];
|
||||
|
||||
foreach (var (glyph, region) in atlasRegions)
|
||||
var (sheetW, sheetH) = sheet.Size;
|
||||
|
||||
if (sheetW - scaled.CurSheetX < img.Width)
|
||||
{
|
||||
atlasDictionary.Add(glyph, new AtlasTexture(texture, region));
|
||||
scaled.CurSheetX = 0;
|
||||
scaled.CurSheetY = scaled.CurSheetMaxY;
|
||||
}
|
||||
|
||||
return (new FontTextureAtlas(texture, atlasDictionary), metricsMap);
|
||||
if (sheetH - scaled.CurSheetY < img.Height)
|
||||
{
|
||||
// Make new sheet.
|
||||
scaled.CurSheetY = 0;
|
||||
scaled.CurSheetX = 0;
|
||||
scaled.CurSheetMaxY = 0;
|
||||
|
||||
sheet = GenSheet();
|
||||
}
|
||||
|
||||
sheet.SetSubImage((scaled.CurSheetX, scaled.CurSheetY), img);
|
||||
|
||||
var atlasTexture = new AtlasTexture(
|
||||
sheet,
|
||||
UIBox2.FromDimensions(
|
||||
scaled.CurSheetX,
|
||||
scaled.CurSheetY,
|
||||
bitmap.Width,
|
||||
bitmap.Rows));
|
||||
|
||||
scaled.AtlasData.Add(glyph, atlasTexture);
|
||||
|
||||
scaled.CurSheetMaxY = Math.Max(scaled.CurSheetMaxY, scaled.CurSheetY + bitmap.Rows);
|
||||
scaled.CurSheetX += bitmap.Width;
|
||||
}
|
||||
else
|
||||
{
|
||||
scaled.AtlasData.Add(glyph, null);
|
||||
}
|
||||
|
||||
scaled.MetricsMap.Add(glyph, metrics);
|
||||
|
||||
OwnedTexture GenSheet()
|
||||
{
|
||||
var sheet = _clyde.CreateBlankTexture<A8>((SheetWidth, SheetHeight),
|
||||
$"font-{face.FamilyName}-{instance.Size}-{(uint) (BaseFontDPI * scale)}-sheet{scaled.AtlasTextures.Count}");
|
||||
scaled.AtlasTextures.Add(sheet);
|
||||
return sheet;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,46 +223,6 @@ namespace Robust.Client.Graphics
|
||||
return bitmapImage;
|
||||
}
|
||||
|
||||
private Dictionary<char, uint> _generateGlyphMap(Face face)
|
||||
{
|
||||
var map = new Dictionary<char, uint>();
|
||||
|
||||
// TODO: Render more than extended ASCII, Cyrillic and Greek. somehow.
|
||||
// Does it make sense to just render every glyph in the font?
|
||||
|
||||
// Render all the extended ASCII characters.
|
||||
// Yeah I know "extended ASCII" isn't a real thing get off my back.
|
||||
for (var i = 32u; i <= 255; i++)
|
||||
{
|
||||
_addGlyph(i, face, map);
|
||||
}
|
||||
|
||||
// Render basic cyrillic.
|
||||
for (var i = 0x0410u; i <= 0x044F; i++)
|
||||
{
|
||||
_addGlyph(i, face, map);
|
||||
}
|
||||
|
||||
// Render greek.
|
||||
for (var i = 0x03B1u; i <= 0x03C9; i++)
|
||||
{
|
||||
_addGlyph(i, face, map);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static void _addGlyph(uint codePoint, Face face, Dictionary<char, uint> map)
|
||||
{
|
||||
var glyphIndex = face.GetCharIndex(codePoint);
|
||||
if (glyphIndex == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
map.Add((char) codePoint, glyphIndex);
|
||||
}
|
||||
|
||||
private class FontFaceHandle : IFontFaceHandle
|
||||
{
|
||||
public Face Face { get; }
|
||||
@@ -282,78 +239,84 @@ namespace Robust.Client.Graphics
|
||||
public FontFaceHandle FaceHandle { get; }
|
||||
public int Size { get; }
|
||||
private readonly Dictionary<float, ScaledFontData> _scaledData = new();
|
||||
public readonly IReadOnlyDictionary<char, uint> GlyphMap;
|
||||
private readonly FontManager _fontManager;
|
||||
public readonly Dictionary<Rune, uint> GlyphMap;
|
||||
|
||||
public FontInstanceHandle(FontManager fontManager, int size, IReadOnlyDictionary<char, uint> glyphMap,
|
||||
FontFaceHandle faceHandle)
|
||||
public FontInstanceHandle(FontManager fontManager, int size, FontFaceHandle faceHandle)
|
||||
{
|
||||
GlyphMap = new Dictionary<Rune, uint>();
|
||||
_fontManager = fontManager;
|
||||
Size = size;
|
||||
GlyphMap = glyphMap;
|
||||
FaceHandle = faceHandle;
|
||||
}
|
||||
|
||||
public Texture? GetCharTexture(char chr, float scale)
|
||||
public Texture? GetCharTexture(Rune codePoint, float scale)
|
||||
{
|
||||
var glyph = _getGlyph(chr);
|
||||
var glyph = GetGlyph(codePoint);
|
||||
if (glyph == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var scaled = _getScaleDatum(scale);
|
||||
scaled.Atlas.AtlasData.TryGetValue(glyph, out var texture);
|
||||
var scaled = GetScaleDatum(scale);
|
||||
_fontManager.CacheGlyph(this, scaled, scale, glyph);
|
||||
|
||||
scaled.AtlasData.TryGetValue(glyph, out var texture);
|
||||
return texture;
|
||||
}
|
||||
|
||||
public CharMetrics? GetCharMetrics(char chr, float scale)
|
||||
public CharMetrics? GetCharMetrics(Rune codePoint, float scale)
|
||||
{
|
||||
var glyph = _getGlyph(chr);
|
||||
var glyph = GetGlyph(codePoint);
|
||||
if (glyph == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var scaled = _getScaleDatum(scale);
|
||||
var scaled = GetScaleDatum(scale);
|
||||
_fontManager.CacheGlyph(this, scaled, scale, glyph);
|
||||
|
||||
return scaled.MetricsMap[glyph];
|
||||
}
|
||||
|
||||
public int GetAscent(float scale)
|
||||
{
|
||||
var scaled = _getScaleDatum(scale);
|
||||
var scaled = GetScaleDatum(scale);
|
||||
return scaled.Ascent;
|
||||
}
|
||||
|
||||
public int GetDescent(float scale)
|
||||
{
|
||||
var scaled = _getScaleDatum(scale);
|
||||
var scaled = GetScaleDatum(scale);
|
||||
return scaled.Descent;
|
||||
}
|
||||
|
||||
public int GetHeight(float scale)
|
||||
{
|
||||
var scaled = _getScaleDatum(scale);
|
||||
var scaled = GetScaleDatum(scale);
|
||||
return scaled.Height;
|
||||
}
|
||||
|
||||
public int GetLineHeight(float scale)
|
||||
{
|
||||
var scaled = _getScaleDatum(scale);
|
||||
var scaled = GetScaleDatum(scale);
|
||||
return scaled.LineHeight;
|
||||
}
|
||||
|
||||
private uint _getGlyph(char chr)
|
||||
private uint GetGlyph(Rune chr)
|
||||
{
|
||||
if (GlyphMap.TryGetValue(chr, out var glyph))
|
||||
{
|
||||
return glyph;
|
||||
}
|
||||
|
||||
return 0;
|
||||
// Check FreeType to see if it exists.
|
||||
var index = FaceHandle.Face.GetCharIndex((uint) chr.Value);
|
||||
|
||||
GlyphMap.Add(chr, index);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private ScaledFontData _getScaleDatum(float scale)
|
||||
private ScaledFontData GetScaleDatum(float scale)
|
||||
{
|
||||
if (_scaledData.TryGetValue(scale, out var datum))
|
||||
{
|
||||
@@ -368,37 +331,25 @@ namespace Robust.Client.Graphics
|
||||
|
||||
private class ScaledFontData
|
||||
{
|
||||
public ScaledFontData(IReadOnlyDictionary<uint, CharMetrics> metricsMap, int ascent, int descent,
|
||||
int height, int lineHeight, FontTextureAtlas atlas)
|
||||
public ScaledFontData(int ascent, int descent, int height, int lineHeight)
|
||||
{
|
||||
MetricsMap = metricsMap;
|
||||
Ascent = ascent;
|
||||
Descent = descent;
|
||||
Height = height;
|
||||
LineHeight = lineHeight;
|
||||
Atlas = atlas;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<uint, CharMetrics> MetricsMap { get; }
|
||||
public int Ascent { get; }
|
||||
public int Descent { get; }
|
||||
public int Height { get; }
|
||||
public int LineHeight { get; }
|
||||
public FontTextureAtlas Atlas { get; }
|
||||
}
|
||||
public readonly List<OwnedTexture> AtlasTextures = new();
|
||||
public readonly Dictionary<uint, AtlasTexture?> AtlasData = new();
|
||||
public readonly Dictionary<uint, CharMetrics> MetricsMap = new();
|
||||
public readonly int Ascent;
|
||||
public readonly int Descent;
|
||||
public readonly int Height;
|
||||
public readonly int LineHeight;
|
||||
|
||||
private class FontTextureAtlas
|
||||
{
|
||||
public FontTextureAtlas(Texture mainTexture, Dictionary<uint, AtlasTexture> atlasData)
|
||||
{
|
||||
MainTexture = mainTexture;
|
||||
AtlasData = atlasData;
|
||||
}
|
||||
|
||||
public Texture MainTexture { get; }
|
||||
|
||||
// Maps glyph index to atlas.
|
||||
public Dictionary<uint, AtlasTexture> AtlasData { get; }
|
||||
public int CurSheetX;
|
||||
public int CurSheetY;
|
||||
public int CurSheetMaxY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
@@ -12,21 +14,42 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies a sub area of the texture with new data.
|
||||
/// </summary>
|
||||
/// <param name="topLeft">The top left corner of the area to modify.</param>
|
||||
/// <param name="sourceImage">The image from which to copy pixel data.</param>
|
||||
/// <param name="sourceRegion">The rectangle inside <paramref name="sourceImage"/> from which to copy.</param>
|
||||
/// <typeparam name="T">
|
||||
/// The type of pixels being used.
|
||||
/// This must match the type used when creating the texture.
|
||||
/// </typeparam>
|
||||
public abstract void SetSubImage<T>(Vector2i topLeft, Image<T> sourceImage, in UIBox2i sourceRegion)
|
||||
where T : unmanaged, IPixel<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Modifies a sub area of the texture with new data.
|
||||
/// </summary>
|
||||
/// <param name="topLeft">The top left corner of the area to modify.</param>
|
||||
/// <param name="sourceImage">The image to paste onto the texture.</param>
|
||||
/// <typeparam name="T">
|
||||
/// The type of pixels being used.
|
||||
/// This must match the type used when creating the texture.
|
||||
/// </typeparam>
|
||||
public void SetSubImage<T>(Vector2i topLeft, Image<T> sourceImage)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
SetSubImage(topLeft, sourceImage, UIBox2i.FromDimensions(0, 0, sourceImage.Width, sourceImage.Height));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
[Obsolete("Use Dispose() instead")]
|
||||
public void Delete()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
~OwnedTexture()
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name","directions"] //'delays' is marked as optional in the spec
|
||||
"required": ["name"] //'delays' is marked as optional in the spec
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
@@ -134,7 +134,8 @@ namespace Robust.Client.Input
|
||||
Priority = p.Priority,
|
||||
Type = p.BindingType,
|
||||
CanFocus = p.CanFocus,
|
||||
CanRepeat = p.CanRepeat
|
||||
CanRepeat = p.CanRepeat,
|
||||
AllowSubCombs = p.AllowSubCombs
|
||||
}).ToArray();
|
||||
|
||||
var leaveEmpty = _modifiedKeyFunctions
|
||||
@@ -203,6 +204,7 @@ namespace Robust.Client.Input
|
||||
|
||||
var bindsDown = new List<KeyBinding>();
|
||||
var hasCanFocus = false;
|
||||
var hasAllowSubCombs = false;
|
||||
|
||||
// bindings are ordered with larger combos before single key bindings so combos have priority.
|
||||
foreach (var binding in _bindings)
|
||||
@@ -221,12 +223,22 @@ namespace Robust.Client.Input
|
||||
matchedCombo = binding.PackedKeyCombo;
|
||||
|
||||
bindsDown.Add(binding);
|
||||
|
||||
hasCanFocus |= binding.CanFocus;
|
||||
hasAllowSubCombs |= binding.AllowSubCombs;
|
||||
|
||||
}
|
||||
else if (PackedIsSubPattern(matchedCombo, binding.PackedKeyCombo))
|
||||
{
|
||||
// kill any lower level matches
|
||||
UpBind(binding);
|
||||
if (hasAllowSubCombs)
|
||||
{
|
||||
bindsDown.Add(binding);
|
||||
}
|
||||
else
|
||||
{
|
||||
// kill any lower level matches
|
||||
UpBind(binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -378,8 +390,8 @@ namespace Robust.Client.Input
|
||||
{
|
||||
for (var i = 0; i < 32; i += 8)
|
||||
{
|
||||
var key = (Key) (subPackedCombo.Packed >> i);
|
||||
if (!PackedContainsKey(packedCombo, key))
|
||||
var key = (Key) ((subPackedCombo.Packed >> i) & 0b_1111_1111);
|
||||
if (key != Key.Unknown && !PackedContainsKey(packedCombo, key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -453,7 +465,7 @@ namespace Robust.Client.Input
|
||||
public IKeyBinding RegisterBinding(BoundKeyFunction function, KeyBindingType bindingType,
|
||||
Key baseKey, Key? mod1, Key? mod2, Key? mod3)
|
||||
{
|
||||
var binding = new KeyBinding(this, function, bindingType, baseKey, false, false,
|
||||
var binding = new KeyBinding(this, function, bindingType, baseKey, false, false, false,
|
||||
0, mod1 ?? Key.Unknown, mod2 ?? Key.Unknown, mod3 ?? Key.Unknown);
|
||||
|
||||
RegisterBinding(binding);
|
||||
@@ -464,7 +476,7 @@ namespace Robust.Client.Input
|
||||
public IKeyBinding RegisterBinding(in KeyBindingRegistration reg, bool markModified = true)
|
||||
{
|
||||
var binding = new KeyBinding(this, reg.Function, reg.Type, reg.BaseKey, reg.CanFocus, reg.CanRepeat,
|
||||
reg.Priority, reg.Mod1, reg.Mod2, reg.Mod3);
|
||||
reg.AllowSubCombs, reg.Priority, reg.Mod1, reg.Mod2, reg.Mod3);
|
||||
|
||||
RegisterBinding(binding, markModified);
|
||||
|
||||
@@ -619,12 +631,18 @@ namespace Robust.Client.Input
|
||||
[ViewVariables]
|
||||
public bool CanRepeat { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the Bound Key Combination allows Sub Combinations of it to trigger.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool AllowSubCombs { get; internal set; }
|
||||
|
||||
[ViewVariables] public int Priority { get; internal set; }
|
||||
|
||||
public KeyBinding(InputManager inputManager, BoundKeyFunction function,
|
||||
KeyBindingType bindingType,
|
||||
Key baseKey,
|
||||
bool canFocus, bool canRepeat, int priority, Key mod1 = Key.Unknown,
|
||||
bool canFocus, bool canRepeat, bool allowSubCombs, int priority, Key mod1 = Key.Unknown,
|
||||
Key mod2 = Key.Unknown,
|
||||
Key mod3 = Key.Unknown)
|
||||
{
|
||||
@@ -632,6 +650,7 @@ namespace Robust.Client.Input
|
||||
BindingType = bindingType;
|
||||
CanFocus = canFocus;
|
||||
CanRepeat = canRepeat;
|
||||
AllowSubCombs = allowSubCombs;
|
||||
Priority = priority;
|
||||
_inputManager = inputManager;
|
||||
|
||||
|
||||
@@ -34,6 +34,29 @@ namespace Robust.Client.Interfaces.Graphics
|
||||
Texture LoadTextureFromImage<T>(Image<T> image, string? name = null,
|
||||
TextureLoadParameters? loadParams = null) where T : unmanaged, IPixel<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a blank texture of the specified parameters.
|
||||
/// This texture can later be modified using <see cref="OwnedTexture.SetSubImage{T}"/>
|
||||
/// </summary>
|
||||
/// <param name="size">The size of the new texture, in pixels.</param>
|
||||
/// <param name="name">A name for the texture that can show up in debugging tools like renderdoc.</param>
|
||||
/// <param name="loadParams">
|
||||
/// Load parameters for the texture describing stuff such as sample mode.
|
||||
/// </param>
|
||||
/// <typeparam name="T">
|
||||
/// The type of pixels to "store" in the texture.
|
||||
/// This is the same type you should pass to <see cref="OwnedTexture.SetSubImage{T}"/>,
|
||||
/// and also determines how the texture is stored internally.
|
||||
/// </typeparam>
|
||||
/// <returns>
|
||||
/// An owned, mutable texture object.
|
||||
/// </returns>
|
||||
OwnedTexture CreateBlankTexture<T>(
|
||||
Vector2i size,
|
||||
string? name = null,
|
||||
in TextureLoadParameters? loadParams = null)
|
||||
where T : unmanaged, IPixel<T>;
|
||||
|
||||
IRenderTexture CreateRenderTarget(Vector2i size, RenderTargetFormatParameters format,
|
||||
TextureSampleParameters? sampleParameters = null, string? name = null);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Robust.Client.Interfaces.Graphics
|
||||
@@ -22,8 +23,13 @@ namespace Robust.Client.Interfaces.Graphics
|
||||
|
||||
internal interface IFontInstanceHandle
|
||||
{
|
||||
Texture? GetCharTexture(char chr, float scale);
|
||||
CharMetrics? GetCharMetrics(char chr, float scale);
|
||||
|
||||
|
||||
Texture? GetCharTexture(Rune codePoint, float scale);
|
||||
Texture? GetCharTexture(char chr, float scale) => GetCharTexture((Rune) chr, scale);
|
||||
CharMetrics? GetCharMetrics(Rune codePoint, float scale);
|
||||
CharMetrics? GetCharMetrics(char chr, float scale) => GetCharMetrics((Rune) chr, scale);
|
||||
|
||||
int GetAscent(float scale);
|
||||
int GetDescent(float scale);
|
||||
int GetHeight(float scale);
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Robust.Client.Interfaces.Input
|
||||
|
||||
bool CanFocus { get; }
|
||||
bool CanRepeat { get; }
|
||||
bool AllowSubCombs { get; }
|
||||
int Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -16,6 +16,7 @@ namespace Robust.Client.Interfaces.Input
|
||||
public int Priority;
|
||||
public bool CanFocus;
|
||||
public bool CanRepeat;
|
||||
public bool AllowSubCombs;
|
||||
|
||||
public void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
@@ -28,6 +29,7 @@ namespace Robust.Client.Interfaces.Input
|
||||
serializer.DataField(ref Priority, "priority", 0);
|
||||
serializer.DataField(ref CanFocus, "canFocus", false);
|
||||
serializer.DataField(ref CanRepeat, "canRepeat", false);
|
||||
serializer.DataField(ref AllowSubCombs, "allowSubCombs", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,22 @@ namespace Robust.Client.Interfaces.UserInterface
|
||||
/// </summary>
|
||||
Stylesheet? Stylesheet { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A control can have "keyboard focus" separate from ControlFocused, obtained when calling
|
||||
/// Control.GrabKeyboardFocus. Corresponding events in Control are KeyboardFocusEntered/Exited
|
||||
/// </summary>
|
||||
Control? KeyboardFocused { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A control gets "ControlFocused" when a mouse button (or any KeyBinding which has CanFocus = true) is
|
||||
/// pressed down on the control. While it is focused, it will receive mouse hover events and the corresponding
|
||||
/// keyup event if it still has focus when that occurs (it will NOT receive the keyup if focus has
|
||||
/// been taken by another control). Focus is removed when a different control takes focus
|
||||
/// (such as by pressing a different mouse button down over a different control) or when the keyup event
|
||||
/// happens. When focus is lost on a control, it always fires Control.ControlFocusExited.
|
||||
/// </summary>
|
||||
Control? ControlFocused { get; }
|
||||
|
||||
LayoutContainer StateRoot { get; }
|
||||
|
||||
LayoutContainer WindowRoot { get; }
|
||||
|
||||
@@ -98,9 +98,9 @@ namespace Robust.Client.Placement
|
||||
public bool Eraser { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The texture we use to show from our placement manager to represent the entity to place
|
||||
/// The texture we use to show from our placement manager to represent the entity to place
|
||||
/// </summary>
|
||||
public IDirectionalTextureProvider? CurrentBaseSprite { get; set; }
|
||||
public List<IDirectionalTextureProvider>? CurrentTextures { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Which of the placement orientations we are trying to place with
|
||||
@@ -311,7 +311,7 @@ namespace Robust.Client.Placement
|
||||
{
|
||||
PlacementChanged?.Invoke(this, EventArgs.Empty);
|
||||
Hijack = null;
|
||||
CurrentBaseSprite = null;
|
||||
CurrentTextures = null;
|
||||
CurrentPrototype = null;
|
||||
CurrentPermission = null;
|
||||
CurrentMode = null;
|
||||
@@ -555,7 +555,7 @@ namespace Robust.Client.Placement
|
||||
{
|
||||
var prototype = _prototypeManager.Index<EntityPrototype>(templateName);
|
||||
|
||||
CurrentBaseSprite = SpriteComponent.GetPrototypeIcon(prototype, ResourceCache);
|
||||
CurrentTextures = SpriteComponent.GetPrototypeTextures(prototype, ResourceCache).ToList();
|
||||
CurrentPrototype = prototype;
|
||||
|
||||
IsActive = true;
|
||||
@@ -563,8 +563,9 @@ namespace Robust.Client.Placement
|
||||
|
||||
private void PreparePlacementTile()
|
||||
{
|
||||
CurrentBaseSprite = ResourceCache
|
||||
.GetResource<TextureResource>(new ResourcePath("/Textures/UserInterface/tilebuildoverlay.png")).Texture;
|
||||
CurrentTextures = new List<IDirectionalTextureProvider>
|
||||
{ResourceCache
|
||||
.GetResource<TextureResource>(new ResourcePath("/Textures/UserInterface/tilebuildoverlay.png")).Texture};
|
||||
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.ClientEye;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
@@ -28,9 +29,9 @@ namespace Robust.Client.Placement
|
||||
public EntityCoordinates MouseCoords { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Texture resource to draw to represent the entity we are tryign to spawn
|
||||
/// Texture resources to draw to represent the entity we are trying to spawn
|
||||
/// </summary>
|
||||
public Texture? SpriteToDraw { get; set; }
|
||||
public List<Texture>? TexturesToDraw { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Color set to the ghost entity when it has a valid spawn position
|
||||
@@ -85,12 +86,15 @@ namespace Robust.Client.Placement
|
||||
|
||||
public virtual void Render(DrawingHandleWorld handle)
|
||||
{
|
||||
if (SpriteToDraw == null)
|
||||
if (TexturesToDraw == null)
|
||||
{
|
||||
SetSprite();
|
||||
DebugTools.AssertNotNull(SpriteToDraw);
|
||||
DebugTools.AssertNotNull(TexturesToDraw);
|
||||
}
|
||||
|
||||
if (TexturesToDraw == null || TexturesToDraw.Count == 0)
|
||||
return;
|
||||
|
||||
IEnumerable<EntityCoordinates> locationcollection;
|
||||
switch (pManager.PlacementType)
|
||||
{
|
||||
@@ -108,13 +112,17 @@ namespace Robust.Client.Placement
|
||||
break;
|
||||
}
|
||||
|
||||
var size = SpriteToDraw!.Size;
|
||||
var size = TexturesToDraw[0].Size;
|
||||
foreach (var coordinate in locationcollection)
|
||||
{
|
||||
var worldPos = coordinate.ToMapPos(pManager.EntityManager);
|
||||
var pos = worldPos - (size/(float)EyeManager.PixelsPerMeter) / 2f;
|
||||
var color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor;
|
||||
handle.DrawTexture(SpriteToDraw, pos, color);
|
||||
|
||||
foreach (var texture in TexturesToDraw)
|
||||
{
|
||||
handle.DrawTexture(texture, pos, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +196,10 @@ namespace Robust.Client.Placement
|
||||
|
||||
public void SetSprite()
|
||||
{
|
||||
SpriteToDraw = pManager.CurrentBaseSprite!.TextureFor(pManager.Direction);
|
||||
if (pManager.CurrentTextures == null)
|
||||
return;
|
||||
|
||||
TexturesToDraw = pManager.CurrentTextures.Select(o => o.TextureFor(pManager.Direction)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -13,12 +13,15 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
public override void Load(IResourceCache cache, ResourcePath path)
|
||||
{
|
||||
if (!cache.ContentFileExists(path))
|
||||
if (!cache.TryContentFileRead(path, out var stream))
|
||||
{
|
||||
throw new FileNotFoundException("Content file does not exist for font");
|
||||
}
|
||||
|
||||
FontFaceHandle = IoCManager.Resolve<IFontManagerInternal>().Load(cache.ContentFileRead(path));
|
||||
using (stream)
|
||||
{
|
||||
FontFaceHandle = IoCManager.Resolve<IFontManagerInternal>().Load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public VectorFont MakeDefault()
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
@@ -83,7 +82,12 @@ namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
// Load image from disk.
|
||||
var texPath = path / (stateObject.StateId + ".png");
|
||||
var image = Image.Load<Rgba32>(cache.ContentFileRead(texPath));
|
||||
var stream = cache.ContentFileRead(texPath);
|
||||
Image<Rgba32> image;
|
||||
using (stream)
|
||||
{
|
||||
image = Image.Load<Rgba32>(stream);
|
||||
}
|
||||
var sheetSize = new Vector2i(image.Width, image.Height);
|
||||
|
||||
if (sheetSize.X % frameSize.X != 0 || sheetSize.Y % frameSize.Y != 0)
|
||||
@@ -341,15 +345,25 @@ namespace Robust.Client.ResourceManagement
|
||||
foreach (var stateObject in manifestJson["states"]!.Cast<JObject>())
|
||||
{
|
||||
var stateName = stateObject["name"]!.ToObject<string>()!;
|
||||
var dirValue = stateObject["directions"]!.ToObject<int>();
|
||||
RSI.State.DirectionType directions;
|
||||
int dirValue;
|
||||
|
||||
var directions = dirValue switch
|
||||
if (stateObject.TryGetValue("directions", out var dirJToken))
|
||||
{
|
||||
1 => RSI.State.DirectionType.Dir1,
|
||||
4 => RSI.State.DirectionType.Dir4,
|
||||
8 => RSI.State.DirectionType.Dir8,
|
||||
_ => throw new RSILoadException($"Invalid direction: {dirValue}")
|
||||
};
|
||||
dirValue= dirJToken.ToObject<int>();
|
||||
directions = dirValue switch
|
||||
{
|
||||
1 => RSI.State.DirectionType.Dir1,
|
||||
4 => RSI.State.DirectionType.Dir4,
|
||||
8 => RSI.State.DirectionType.Dir8,
|
||||
_ => throw new RSILoadException($"Invalid direction: {dirValue} expected 1, 4 or 8")
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
dirValue = 1;
|
||||
directions = RSI.State.DirectionType.Dir1;
|
||||
}
|
||||
|
||||
// We can ignore selectors and flags for now,
|
||||
// because they're not used yet!
|
||||
|
||||
@@ -26,20 +26,23 @@ namespace Robust.Client.ResourceManagement
|
||||
throw new FileNotFoundException("Content file does not exist for texture");
|
||||
}
|
||||
|
||||
// Primarily for tracking down iCCP sRGB errors in the image files.
|
||||
Logger.DebugS("res.tex", $"Loading texture {path}.");
|
||||
|
||||
var loadParameters = _tryLoadTextureParameters(cache, path) ?? TextureLoadParameters.Default;
|
||||
|
||||
var manager = IoCManager.Resolve<IClyde>();
|
||||
|
||||
using var image = Image.Load<Rgba32>(stream);
|
||||
|
||||
Texture = manager.LoadTextureFromImage(image, path.ToString(), loadParameters);
|
||||
|
||||
if (cache is IResourceCacheInternal cacheInternal)
|
||||
using (stream)
|
||||
{
|
||||
cacheInternal.TextureLoaded(new TextureLoadedEventArgs(path, image, this));
|
||||
// Primarily for tracking down iCCP sRGB errors in the image files.
|
||||
Logger.DebugS("res.tex", $"Loading texture {path}.");
|
||||
|
||||
var loadParameters = _tryLoadTextureParameters(cache, path) ?? TextureLoadParameters.Default;
|
||||
|
||||
var manager = IoCManager.Resolve<IClyde>();
|
||||
|
||||
using var image = Image.Load<Rgba32>(stream);
|
||||
|
||||
Texture = manager.LoadTextureFromImage(image, path.ToString(), loadParameters);
|
||||
|
||||
if (cache is IResourceCacheInternal cacheInternal)
|
||||
{
|
||||
cacheInternal.TextureLoaded(new TextureLoadedEventArgs(path, image, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,21 +51,25 @@ namespace Robust.Client.ResourceManagement
|
||||
var metaPath = path.WithName(path.Filename + ".yml");
|
||||
if (cache.TryContentFileRead(metaPath, out var stream))
|
||||
{
|
||||
YamlDocument yamlData;
|
||||
using (var reader = new StreamReader(stream, EncodingHelpers.UTF8))
|
||||
using (stream)
|
||||
{
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(reader);
|
||||
if (yamlStream.Documents.Count == 0)
|
||||
YamlDocument yamlData;
|
||||
using (var reader = new StreamReader(stream, EncodingHelpers.UTF8))
|
||||
{
|
||||
return null;
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(reader);
|
||||
if (yamlStream.Documents.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
yamlData = yamlStream.Documents[0];
|
||||
}
|
||||
|
||||
yamlData = yamlStream.Documents[0];
|
||||
return TextureLoadParameters.FromYaml((YamlMappingNode) yamlData.RootNode);
|
||||
}
|
||||
|
||||
return TextureLoadParameters.FromYaml((YamlMappingNode)yamlData.RootNode);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,4 +45,10 @@
|
||||
|
||||
<EmbeddedResource Include="Graphics\Clyde\Shaders\*" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\MSBuild\Robust.Engine.targets" />
|
||||
<PropertyGroup>
|
||||
<RobustToolsPath>../Tools</RobustToolsPath>
|
||||
</PropertyGroup>
|
||||
<Target Name="RobustAfterBuild" AfterTargets="Build" />
|
||||
<Import Project="..\MSBuild\XamlIL.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -6,6 +6,7 @@ using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.Interfaces.Graphics;
|
||||
using Robust.Client.Interfaces.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -49,6 +50,45 @@ namespace Robust.Client.UserInterface
|
||||
[ViewVariables]
|
||||
public Control? Parent { get; private set; }
|
||||
|
||||
public NameScope? NameScope;
|
||||
|
||||
//public void AttachNameScope(Dictionary<string, Control> nameScope)
|
||||
//{
|
||||
// _nameScope = nameScope;
|
||||
//}
|
||||
|
||||
public NameScope? FindNameScope()
|
||||
{
|
||||
foreach (var control in this.GetSelfAndLogicalAncestors())
|
||||
{
|
||||
if (control.NameScope != null) return control.NameScope;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public T FindControl<T>(string name) where T : Control
|
||||
{
|
||||
var nameScope = FindNameScope();
|
||||
if (nameScope == null)
|
||||
{
|
||||
throw new ArgumentException("No Namespace found for Control");
|
||||
}
|
||||
|
||||
var value = nameScope.Find(name);
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentException($"No Control with the name {name} found");
|
||||
}
|
||||
|
||||
if (value is not T ret)
|
||||
{
|
||||
throw new ArgumentException($"Control with name {name} had invalid type {value.GetType()}");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal IUserInterfaceManagerInternal UserInterfaceManagerInternal { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -62,6 +102,9 @@ namespace Robust.Client.UserInterface
|
||||
[ViewVariables]
|
||||
public OrderedChildCollection Children { get; }
|
||||
|
||||
[Content]
|
||||
public virtual ICollection<Control> XamlChildren { get; protected set; }
|
||||
|
||||
[ViewVariables] public int ChildCount => _orderedChildren.Count;
|
||||
|
||||
/// <summary>
|
||||
@@ -394,6 +437,7 @@ namespace Robust.Client.UserInterface
|
||||
UserInterfaceManagerInternal = IoCManager.Resolve<IUserInterfaceManagerInternal>();
|
||||
StyleClasses = new StyleClassCollection(this);
|
||||
Children = new OrderedChildCollection(this);
|
||||
XamlChildren = Children;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -477,7 +521,7 @@ namespace Robust.Client.UserInterface
|
||||
{
|
||||
DebugTools.Assert(!Disposed, "Control has been disposed.");
|
||||
|
||||
foreach (var child in Children.ToList())
|
||||
foreach (var child in Children.ToArray())
|
||||
{
|
||||
RemoveChild(child);
|
||||
}
|
||||
@@ -713,14 +757,32 @@ namespace Robust.Client.UserInterface
|
||||
/// <summary>
|
||||
/// Called when this control receives keyboard focus.
|
||||
/// </summary>
|
||||
protected internal virtual void FocusEntered()
|
||||
protected internal virtual void KeyboardFocusEntered()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when this control loses keyboard focus.
|
||||
/// Called when this control loses keyboard focus (corresponds to UserInterfaceManager.KeyboardFocused).
|
||||
/// </summary>
|
||||
protected internal virtual void FocusExited()
|
||||
protected internal virtual void KeyboardFocusExited()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a control loses control focus for any reason. See <see cref="IUserInterfaceManager.ControlFocused"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Controls which have some sort of drag / drop behavior should usually implement this method (typically by cancelling the drag drop).
|
||||
/// Otherwise, if a user clicks down LMB over one control to initiate a drag, then clicks RMB down
|
||||
/// over a different control while still holding down LMB, the control being dragged will now lose focus
|
||||
/// and will no longer receive the keyup for the LMB, thus won't cancel the drag.
|
||||
/// This should also be considered for controls which have any special KeyBindUp behavior - consider
|
||||
/// what would happen if the control lost focus and never received the KeyBindUp.
|
||||
///
|
||||
/// There is no corresponding ControlFocusEntered - if a control wants to handle that situation they should simply
|
||||
/// handle KeyBindDown as that's the only way a control would gain focus.
|
||||
/// </remarks>
|
||||
protected internal virtual void ControlFocusExited()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -433,7 +433,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
if (item.Region == null)
|
||||
continue;
|
||||
|
||||
if (!item.Region.Value.Contains(args.RelativePosition))
|
||||
if (!item.Region.Value.Contains(args.RelativePixelPosition))
|
||||
continue;
|
||||
|
||||
if (item.Selectable && !item.Disabled)
|
||||
|
||||
@@ -589,18 +589,18 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return index;
|
||||
}
|
||||
|
||||
protected internal override void FocusEntered()
|
||||
protected internal override void KeyboardFocusEntered()
|
||||
{
|
||||
base.FocusEntered();
|
||||
base.KeyboardFocusEntered();
|
||||
|
||||
// Reset this so the cursor is always visible immediately after gaining focus..
|
||||
_resetCursorBlink();
|
||||
OnFocusEnter?.Invoke(new LineEditEventArgs(this, _text));
|
||||
}
|
||||
|
||||
protected internal override void FocusExited()
|
||||
protected internal override void KeyboardFocusExited()
|
||||
{
|
||||
base.FocusExited();
|
||||
base.KeyboardFocusExited();
|
||||
OnFocusExit?.Invoke(new LineEditEventArgs(this, _text));
|
||||
}
|
||||
|
||||
|
||||
264
Robust.Client/UserInterface/Controls/RadioOptions.cs
Normal file
264
Robust.Client/UserInterface/Controls/RadioOptions.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
using Robust.Client.Graphics;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
public enum RadioOptionsLayout { Horizontal, Vertical }
|
||||
|
||||
public class RadioOptions<T> : Control
|
||||
{
|
||||
private int internalIdCount = 0;
|
||||
|
||||
private readonly List<RadioOptionButtonData<T>> _buttonDataList = new();
|
||||
//private readonly Dictionary<int, int> _idMap = new();
|
||||
private ButtonGroup _buttonGroup = new();
|
||||
private Container _container;
|
||||
|
||||
public string ButtonStyle = string.Empty;
|
||||
public string FirstButtonStyle = string.Empty;
|
||||
public string LastButtonStyle = string.Empty;
|
||||
|
||||
public int ItemCount => _buttonDataList.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever you select a button.
|
||||
///
|
||||
/// Note: You should add optionButtons.Select(args.Id); if you want to actually select the button.
|
||||
/// </summary>
|
||||
public event Action<RadioOptionItemSelectedEventArgs<T>>? OnItemSelected;
|
||||
|
||||
public RadioOptions(RadioOptionsLayout layout)
|
||||
{
|
||||
switch (layout)
|
||||
{
|
||||
case RadioOptionsLayout.Vertical:
|
||||
_container = new VBoxContainer();
|
||||
break;
|
||||
case RadioOptionsLayout.Horizontal:
|
||||
default:
|
||||
_container = new HBoxContainer();
|
||||
break;
|
||||
}
|
||||
|
||||
this.AddChild(_container);
|
||||
}
|
||||
|
||||
public int AddItem(string label, T value, Action<RadioOptionItemSelectedEventArgs<T>>? itemSelectedAction = null)
|
||||
{
|
||||
var button = new Button
|
||||
{
|
||||
Text = label,
|
||||
Group = _buttonGroup
|
||||
};
|
||||
|
||||
button.OnPressed += ButtonOnPressed;
|
||||
|
||||
var data = new RadioOptionButtonData<T>(label, value, button)
|
||||
{
|
||||
Id = internalIdCount++
|
||||
};
|
||||
|
||||
if (itemSelectedAction != null)
|
||||
{
|
||||
data.OnItemSelected += itemSelectedAction;
|
||||
}
|
||||
|
||||
_buttonDataList.Add(data);
|
||||
_container.AddChild(button);
|
||||
UpdateFirstAndLastButtonStyle();
|
||||
|
||||
if (_buttonDataList.Count == 1)
|
||||
{
|
||||
Select(data.Id);
|
||||
}
|
||||
return data.Id;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This is triggered when the button is pressed via the UI
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
private void ButtonOnPressed(ButtonEventArgs obj)
|
||||
{
|
||||
var buttonData = _buttonDataList.FirstOrDefault(bd => bd.Button == obj.Button);
|
||||
if (buttonData != null)
|
||||
{
|
||||
InvokeItemSelected(new RadioOptionItemSelectedEventArgs<T>(buttonData.Id, this));
|
||||
return;
|
||||
}
|
||||
// Not reachable.
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var buttonDatum in _buttonDataList)
|
||||
{
|
||||
buttonDatum.Button.OnPressed -= ButtonOnPressed;
|
||||
}
|
||||
_buttonDataList.Clear();
|
||||
_container.Children.Clear();
|
||||
SelectedId = 0;
|
||||
}
|
||||
|
||||
public object? GetItemMetadata(int idx)
|
||||
{
|
||||
return _buttonDataList.FirstOrDefault(bd => bd.Id == idx)?.Metadata;
|
||||
}
|
||||
|
||||
public int SelectedId { get; private set; }
|
||||
public RadioOptionButtonData<T> SelectedButtonData => _buttonDataList.First(bd => bd.Id == SelectedId);
|
||||
public Button SelectedButton => SelectedButtonData.Button;
|
||||
public string SelectedText => SelectedButtonData.Text;
|
||||
public T SelectedValue => SelectedButtonData.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Always will return true if itemId is not found.
|
||||
/// </summary>
|
||||
/// <param name="idx"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsItemDisabled(int idx)
|
||||
{
|
||||
return _buttonDataList.FirstOrDefault(bd => bd.Id == idx)?.Disabled ?? true;
|
||||
}
|
||||
|
||||
public void RemoveItem(int idx)
|
||||
{
|
||||
|
||||
var data = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (data!= null)
|
||||
{
|
||||
data.Button.OnPressed -= ButtonOnPressed;
|
||||
_container.RemoveChild(data.Button);
|
||||
|
||||
var buttonData = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (buttonData != null)
|
||||
_buttonDataList.Remove(buttonData);
|
||||
|
||||
UpdateFirstAndLastButtonStyle();
|
||||
}
|
||||
}
|
||||
|
||||
public void Select(int idx)
|
||||
{
|
||||
var data = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (data != null)
|
||||
{
|
||||
SelectedId = data.Id;
|
||||
data.Button.Pressed = true;
|
||||
return;
|
||||
}
|
||||
// Not found.
|
||||
}
|
||||
|
||||
public void SelectByValue(T value)
|
||||
{
|
||||
var data = _buttonDataList.FirstOrDefault(bd => EqualityComparer<T>.Default.Equals(bd.Value, value));
|
||||
if (data != null)
|
||||
{
|
||||
Select(data.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public void InvokeItemSelected(RadioOptionItemSelectedEventArgs<T> args)
|
||||
{
|
||||
var buttonData = _buttonDataList.FirstOrDefault(bd => bd.Id == args.Id);
|
||||
if (buttonData == null) return;
|
||||
|
||||
if (buttonData.HasOnItemSelectedEvent)
|
||||
buttonData.InvokeItemSelected(args);
|
||||
else
|
||||
OnItemSelected?.Invoke(args);
|
||||
}
|
||||
|
||||
public void UpdateFirstAndLastButtonStyle()
|
||||
{
|
||||
for (int i = 0; i < _buttonDataList.Count; i++)
|
||||
{
|
||||
var buttonData = _buttonDataList[i];
|
||||
if (buttonData.Button == null) continue;
|
||||
|
||||
buttonData.Button.StyleClasses.Remove(ButtonStyle);
|
||||
buttonData.Button.StyleClasses.Remove(LastButtonStyle);
|
||||
buttonData.Button.StyleClasses.Remove(FirstButtonStyle);
|
||||
|
||||
if (i == 0)
|
||||
buttonData.Button.StyleClasses.Add(FirstButtonStyle);
|
||||
else if (i == _buttonDataList.Count - 1)
|
||||
buttonData.Button.StyleClasses.Add(LastButtonStyle);
|
||||
else
|
||||
buttonData.Button.StyleClasses.Add(ButtonStyle);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetItemDisabled(int idx, bool disabled)
|
||||
{
|
||||
var data = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (data != null)
|
||||
{
|
||||
data.Disabled = disabled;
|
||||
data.Button.Disabled = disabled;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetItemMetadata(int idx, object metadata)
|
||||
{
|
||||
var buttonData = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (buttonData != null)
|
||||
buttonData.Metadata = metadata;
|
||||
}
|
||||
|
||||
public void SetItemText(int idx, string text)
|
||||
{
|
||||
var data = _buttonDataList.FirstOrDefault(bd => bd.Id == idx);
|
||||
if (data != null)
|
||||
{
|
||||
data.Text = text;
|
||||
data.Button.Text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
public class RadioOptionItemSelectedEventArgs<T> : EventArgs
|
||||
{
|
||||
public RadioOptions<T> Button { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the item that has been selected.
|
||||
/// </summary>
|
||||
public int Id { get; }
|
||||
|
||||
public RadioOptionItemSelectedEventArgs(int id, RadioOptions<T> button)
|
||||
{
|
||||
Id = id;
|
||||
Button = button;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RadioOptionButtonData<T>
|
||||
{
|
||||
public int Id;
|
||||
public string Text;
|
||||
public T Value;
|
||||
public bool Disabled;
|
||||
public object? Metadata;
|
||||
|
||||
public Button Button;
|
||||
|
||||
public RadioOptionButtonData(string text, T value, Button button)
|
||||
{
|
||||
Text = text;
|
||||
this.Button = button;
|
||||
Value = value;
|
||||
}
|
||||
public event Action<RadioOptionItemSelectedEventArgs<T>>? OnItemSelected;
|
||||
public bool HasOnItemSelectedEvent => OnItemSelected != null;
|
||||
public void InvokeItemSelected(RadioOptionItemSelectedEventArgs<T> args)
|
||||
{
|
||||
OnItemSelected?.Invoke(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,11 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (!VisibleInTree)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_label.Text = string.Join("\n", _inputManager.DownKeyFunctions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,11 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
return;
|
||||
}
|
||||
|
||||
if (!VisibleInTree)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetManager.IsConnected)
|
||||
{
|
||||
contents.Text = "Not connected to server.";
|
||||
|
||||
15
Robust.Client/UserInterface/CustomControls/SS14Window.xaml
Normal file
15
Robust.Client/UserInterface/CustomControls/SS14Window.xaml
Normal file
@@ -0,0 +1,15 @@
|
||||
<cc:SS14Window xmlns:cc="clr-namespace:Robust.Client.UserInterface.CustomControls"
|
||||
xmlns:c="clr-namespace:Robust.Client.UserInterface.Controls">
|
||||
<c:PanelContainer StyleClasses="windowPanel"/>
|
||||
<c:VBoxContainer SeparationOverride="0">
|
||||
<c:PanelContainer Name="WindowHeader" StyleClasses="windowHeader">
|
||||
<c:HBoxContainer>
|
||||
<c:MarginContainer MarginLeftOverride="5" SizeFlagsHorizontal="FillExpand">
|
||||
<c:Label Name="TitleLabel" StyleIdentifier="foo" ClipText="True" Text="Exemplary Window Title Here" VAlign="Center" StyleClasses="windowTitle"/>
|
||||
</c:MarginContainer>
|
||||
<c:TextureButton Name="CloseButton" StyleClasses="windowCloseButton" SizeFlagsVertical="ShrinkCenter"></c:TextureButton>
|
||||
</c:HBoxContainer>
|
||||
</c:PanelContainer>
|
||||
<c:MarginContainer Name="ContentsContainer" MarginBottomOverride="10" MarginLeftOverride="10" MarginRightOverride="10" MarginTopOverride="10" RectClipContent="True" SizeFlagsVertical="FillExpand" />
|
||||
</c:VBoxContainer>
|
||||
</cc:SS14Window>
|
||||
@@ -1,12 +1,17 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.CustomControls
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public class SS14Window : BaseWindow
|
||||
public partial class SS14Window : BaseWindow
|
||||
{
|
||||
public const string StyleClassWindowTitle = "windowTitle";
|
||||
public const string StyleClassWindowPanel = "windowPanel";
|
||||
@@ -17,70 +22,19 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
public SS14Window()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
AddChild(new PanelContainer
|
||||
{
|
||||
StyleClasses = {StyleClassWindowPanel}
|
||||
});
|
||||
WindowHeader.CustomMinimumSize = (0, HEADER_SIZE_Y);
|
||||
|
||||
AddChild(new VBoxContainer
|
||||
{
|
||||
SeparationOverride = 0,
|
||||
Children =
|
||||
{
|
||||
new PanelContainer
|
||||
{
|
||||
StyleClasses = {StyleClassWindowHeader},
|
||||
CustomMinimumSize = (0, HEADER_SIZE_Y),
|
||||
Children =
|
||||
{
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new MarginContainer
|
||||
{
|
||||
MarginLeftOverride = 5,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
(TitleLabel = new Label
|
||||
{
|
||||
StyleIdentifier = "foo",
|
||||
ClipText = true,
|
||||
Text = "Exemplary Window Title Here",
|
||||
VAlign = Label.VAlignMode.Center,
|
||||
StyleClasses = {StyleClassWindowTitle}
|
||||
})
|
||||
}
|
||||
},
|
||||
(CloseButton = new TextureButton
|
||||
{
|
||||
StyleClasses = {StyleClassWindowCloseButton},
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
(Contents = new MarginContainer
|
||||
{
|
||||
MarginBottomOverride = 10,
|
||||
MarginLeftOverride = 10,
|
||||
MarginRightOverride = 10,
|
||||
MarginTopOverride = 10,
|
||||
RectClipContent = true,
|
||||
SizeFlagsVertical = SizeFlags.FillExpand
|
||||
})
|
||||
}
|
||||
});
|
||||
Contents = ContentsContainer;
|
||||
|
||||
CloseButton.OnPressed += CloseButtonPressed;
|
||||
XamlChildren = new SS14ContentCollection(this);
|
||||
}
|
||||
|
||||
public MarginContainer Contents { get; private set; }
|
||||
private TextureButton CloseButton;
|
||||
//private TextureButton CloseButton;
|
||||
|
||||
private const int DRAG_MARGIN_SIZE = 7;
|
||||
|
||||
@@ -103,7 +57,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
}
|
||||
}
|
||||
|
||||
private Label TitleLabel;
|
||||
//private Label TitleLabel;
|
||||
|
||||
public string? Title
|
||||
{
|
||||
@@ -177,5 +131,91 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
public class SS14ContentCollection : ICollection<Control>, IReadOnlyCollection<Control>
|
||||
{
|
||||
private readonly SS14Window Owner;
|
||||
|
||||
public SS14ContentCollection(SS14Window owner)
|
||||
{
|
||||
Owner = owner;
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new(Owner);
|
||||
}
|
||||
|
||||
IEnumerator<Control> IEnumerable<Control>.GetEnumerator() => GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void Add(Control item)
|
||||
{
|
||||
Owner.Contents.AddChild(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Owner.Contents.RemoveAllChildren();
|
||||
}
|
||||
|
||||
public bool Contains(Control item)
|
||||
{
|
||||
return item?.Parent == Owner.Contents;
|
||||
}
|
||||
|
||||
public void CopyTo(Control[] array, int arrayIndex)
|
||||
{
|
||||
Owner.Contents.Children.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public bool Remove(Control item)
|
||||
{
|
||||
if (item?.Parent != Owner.Contents)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DebugTools.AssertNotNull(Owner?.Contents);
|
||||
Owner!.Contents.RemoveChild(item);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int ICollection<Control>.Count => Owner.Contents.ChildCount;
|
||||
int IReadOnlyCollection<Control>.Count => Owner.Contents.ChildCount;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
|
||||
public struct Enumerator : IEnumerator<Control>
|
||||
{
|
||||
private OrderedChildCollection.Enumerator _enumerator;
|
||||
|
||||
internal Enumerator(SS14Window ss14Window)
|
||||
{
|
||||
_enumerator = ss14Window.Contents.Children.GetEnumerator();
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
return _enumerator.MoveNext();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
((IEnumerator) _enumerator).Reset();
|
||||
}
|
||||
|
||||
public Control Current => _enumerator.Current;
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_enumerator.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +179,17 @@ namespace Robust.Client.UserInterface
|
||||
return tcs.Task;
|
||||
#else
|
||||
// Luckily, GTK Linux and COM Windows are both happily threaded. Yay!
|
||||
return Task.Run(action);
|
||||
// * Actual attempts to have multiple file dialogs up at the same time, and the resulting crashes,
|
||||
// have shown that at least for GTK+ (Linux), just because it can handle being on any thread doesn't mean it handle being on two at the same time.
|
||||
// Testing system was Ubuntu 20.04.
|
||||
// COM on Windows might handle this, but honestly, who exactly wants to risk it?
|
||||
// In particular this could very well be an swnfd issue.
|
||||
return Task.Run(() =>
|
||||
{
|
||||
lock (this) {
|
||||
return action();
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
17
Robust.Client/UserInterface/LogicalExtensions.cs
Normal file
17
Robust.Client/UserInterface/LogicalExtensions.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Robust.Client.UserInterface
|
||||
{
|
||||
public static class LogicalExtensions
|
||||
{
|
||||
public static IEnumerable<Control> GetSelfAndLogicalAncestors(this Control control)
|
||||
{
|
||||
Control? c = control;
|
||||
while (c != null)
|
||||
{
|
||||
yield return c;
|
||||
c = c.Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -172,8 +173,29 @@ namespace Robust.Client.UserInterface
|
||||
// This needs to happen because word wrapping doesn't get checked for the last word.
|
||||
if (posX > maxSizeX)
|
||||
{
|
||||
DebugTools.Assert(wordStartBreakIndex.HasValue,
|
||||
"wordStartBreakIndex can only be null if the word begins at a new line, in which case this branch shouldn't be reached as the word would be split due to being longer than a single line.");
|
||||
if (!wordStartBreakIndex.HasValue)
|
||||
{
|
||||
Logger.Error(
|
||||
"Assert fail inside RichTextEntry.Update, " +
|
||||
"wordStartBreakIndex is null on method end w/ word wrap required. " +
|
||||
"Dumping relevant stuff. Send this to PJB.");
|
||||
Logger.Error($"Message: {Message}");
|
||||
Logger.Error($"maxSizeX: {maxSizeX}");
|
||||
Logger.Error($"maxUsedWidth: {maxUsedWidth}");
|
||||
Logger.Error($"breakIndexCounter: {breakIndexCounter}");
|
||||
Logger.Error("wordStartBreakIndex: null (duh)");
|
||||
Logger.Error($"wordSizePixels: {wordSizePixels}");
|
||||
Logger.Error($"posX: {posX}");
|
||||
Logger.Error($"lastChar: {lastChar}");
|
||||
Logger.Error($"forceSplitData: {forceSplitData}");
|
||||
Logger.Error($"LineBreaks: {string.Join(", ", LineBreaks)}");
|
||||
|
||||
throw new Exception(
|
||||
"wordStartBreakIndex can only be null if the word begins at a new line," +
|
||||
"in which case this branch shouldn't be reached as" +
|
||||
"the word would be split due to being longer than a single line.");
|
||||
}
|
||||
|
||||
LineBreaks.Add(wordStartBreakIndex!.Value.index);
|
||||
Height += font.GetLineHeight(uiScale);
|
||||
maxUsedWidth = Math.Max(maxUsedWidth, wordStartBreakIndex.Value.lineSize);
|
||||
|
||||
@@ -57,9 +57,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
public Control? KeyboardFocused { get; private set; }
|
||||
|
||||
// When a control receives a mouse down it must also receive a mouse up and mouse moves, always.
|
||||
// So we keep track of which control is "focused" by the mouse.
|
||||
private Control? _controlFocused;
|
||||
public Control? ControlFocused { get; private set; }
|
||||
|
||||
public LayoutContainer StateRoot { get; private set; } = default!;
|
||||
public PopupContainer ModalRoot { get; private set; } = default!;
|
||||
@@ -244,7 +242,8 @@ namespace Robust.Client.UserInterface
|
||||
RemoveModal(top);
|
||||
else
|
||||
{
|
||||
_controlFocused = top;
|
||||
ControlFocused?.ControlFocusExited();
|
||||
ControlFocused = top;
|
||||
return false; // prevent anything besides the top modal control from receiving input
|
||||
}
|
||||
}
|
||||
@@ -260,12 +259,12 @@ namespace Robust.Client.UserInterface
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ControlFocused?.ControlFocusExited();
|
||||
ControlFocused = control;
|
||||
|
||||
_controlFocused = control;
|
||||
|
||||
if (_controlFocused.CanKeyboardFocus && _controlFocused.KeyboardFocusOnClick)
|
||||
if (ControlFocused.CanKeyboardFocus && ControlFocused.KeyboardFocusOnClick)
|
||||
{
|
||||
_controlFocused.GrabKeyboardFocus();
|
||||
ControlFocused.GrabKeyboardFocus();
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -273,7 +272,8 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
public void HandleCanFocusUp()
|
||||
{
|
||||
_controlFocused = null;
|
||||
ControlFocused?.ControlFocusExited();
|
||||
ControlFocused = null;
|
||||
}
|
||||
|
||||
public void KeyBindDown(BoundKeyEventArgs args)
|
||||
@@ -290,7 +290,7 @@ namespace Robust.Client.UserInterface
|
||||
return;
|
||||
}
|
||||
|
||||
var control = _controlFocused ?? KeyboardFocused ?? MouseGetControl(args.PointerLocation.Position);
|
||||
var control = ControlFocused ?? KeyboardFocused ?? MouseGetControl(args.PointerLocation.Position);
|
||||
|
||||
if (control == null)
|
||||
{
|
||||
@@ -311,7 +311,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
public void KeyBindUp(BoundKeyEventArgs args)
|
||||
{
|
||||
var control = _controlFocused ?? KeyboardFocused ?? MouseGetControl(args.PointerLocation.Position);
|
||||
var control = ControlFocused ?? KeyboardFocused ?? MouseGetControl(args.PointerLocation.Position);
|
||||
if (control == null)
|
||||
{
|
||||
return;
|
||||
@@ -352,7 +352,7 @@ namespace Robust.Client.UserInterface
|
||||
_needUpdateActiveCursor = true;
|
||||
}
|
||||
|
||||
var target = _controlFocused ?? newHovered;
|
||||
var target = ControlFocused ?? newHovered;
|
||||
if (target != null)
|
||||
{
|
||||
var guiArgs = new GUIMouseMoveEventArgs(mouseMoveEventArgs.Relative / UIScale,
|
||||
@@ -368,7 +368,7 @@ namespace Robust.Client.UserInterface
|
||||
private void UpdateActiveCursor()
|
||||
{
|
||||
// Consider mouse input focus first so that dragging windows don't act up etc.
|
||||
var cursorTarget = _controlFocused ?? CurrentlyHovered;
|
||||
var cursorTarget = ControlFocused ?? CurrentlyHovered;
|
||||
|
||||
if (cursorTarget == null)
|
||||
{
|
||||
@@ -478,13 +478,13 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
KeyboardFocused = control;
|
||||
|
||||
KeyboardFocused.FocusEntered();
|
||||
KeyboardFocused.KeyboardFocusEntered();
|
||||
}
|
||||
|
||||
public void ReleaseKeyboardFocus()
|
||||
{
|
||||
var oldFocused = KeyboardFocused;
|
||||
oldFocused?.FocusExited();
|
||||
oldFocused?.KeyboardFocusExited();
|
||||
KeyboardFocused = null;
|
||||
}
|
||||
|
||||
@@ -528,10 +528,9 @@ namespace Robust.Client.UserInterface
|
||||
_clearTooltip();
|
||||
}
|
||||
|
||||
if (control == _controlFocused)
|
||||
{
|
||||
_controlFocused = null;
|
||||
}
|
||||
if (control != ControlFocused) return;
|
||||
ControlFocused?.ControlFocusExited();
|
||||
ControlFocused = null;
|
||||
}
|
||||
|
||||
public void PushModal(Control modal)
|
||||
@@ -569,7 +568,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
public void CursorChanged(Control control)
|
||||
{
|
||||
if (control == _controlFocused || control == CurrentlyHovered)
|
||||
if (control == ControlFocused || control == CurrentlyHovered)
|
||||
{
|
||||
_needUpdateActiveCursor = true;
|
||||
}
|
||||
|
||||
42
Robust.Client/UserInterface/XAML/Attributes.cs
Normal file
42
Robust.Client/UserInterface/XAML/Attributes.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.UserInterface.XAML
|
||||
{
|
||||
public class ContentAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
public class XmlnsDefinitionAttribute : Attribute
|
||||
{
|
||||
public XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class UsableDuringInitializationAttribute : Attribute
|
||||
{
|
||||
public UsableDuringInitializationAttribute(bool usable)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class DeferredContentAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
public interface ITestRootObjectProvider
|
||||
{
|
||||
object RootObject { get; }
|
||||
}
|
||||
|
||||
public interface ITestProvideValueTarget
|
||||
{
|
||||
object TargetObject { get; }
|
||||
object TargetProperty { get; }
|
||||
}
|
||||
|
||||
public interface ITestUriContext
|
||||
{
|
||||
Uri BaseUri { get; set; }
|
||||
}
|
||||
}
|
||||
72
Robust.Client/UserInterface/XAML/NameScope.cs
Normal file
72
Robust.Client/UserInterface/XAML/NameScope.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Robust.Client.UserInterface.XAML
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a name scope.
|
||||
/// </summary>
|
||||
public class NameScope
|
||||
{
|
||||
public bool IsCompleted { get; private set; }
|
||||
|
||||
private readonly Dictionary<string, Control> _inner = new Dictionary<string, Control>();
|
||||
|
||||
public void Register(string name, Control element)
|
||||
{
|
||||
if (IsCompleted)
|
||||
throw new InvalidOperationException("NameScope is completed, no further registrations are allowed");
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
if (element == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(element));
|
||||
}
|
||||
|
||||
if (_inner.TryGetValue(name, out Control? existing))
|
||||
{
|
||||
if (existing != element)
|
||||
{
|
||||
throw new ArgumentException($"Control with the name '{name}' already registered.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_inner.Add(name, element);
|
||||
}
|
||||
}
|
||||
|
||||
public void Absorb(NameScope? nameScope)
|
||||
{
|
||||
if (nameScope == null) return;
|
||||
|
||||
foreach (var (name, control) in nameScope._inner)
|
||||
{
|
||||
try
|
||||
{
|
||||
Register(name, control);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ArgumentException($"Exception occured when trying to absorb NameScope (at name {name})", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Control? Find(string name)
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
|
||||
_inner.TryGetValue(name, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
IsCompleted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Robust.Client/UserInterface/XAML/RobustXamlLoader.cs
Normal file
13
Robust.Client/UserInterface/XAML/RobustXamlLoader.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.UserInterface.XAML
|
||||
{
|
||||
public class RobustXamlLoader
|
||||
{
|
||||
public static void Load(object obj)
|
||||
{
|
||||
throw new Exception(
|
||||
$"No precompiled XAML found for {obj.GetType()}, make sure to specify Class or name your class the same as your .xaml ");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using JetBrains.Annotations;
|
||||
|
||||
namespace Robust.Client.Utility
|
||||
{
|
||||
public static class UserDataDir
|
||||
internal static class UserDataDir
|
||||
{
|
||||
[Pure]
|
||||
public static string GetUserDataDir()
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.ViewVariables.Traits;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -51,7 +52,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
|
||||
private bool _serverLoaded;
|
||||
|
||||
public ViewVariablesInstanceEntity(IViewVariablesManagerInternal vvm, IResourceCache resCache, IEntityManager entityManager) : base(vvm, resCache)
|
||||
public ViewVariablesInstanceEntity(IViewVariablesManagerInternal vvm, IEntityManager entityManager, IRobustSerializer robustSerializer) : base(vvm, robustSerializer)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
}
|
||||
@@ -117,7 +118,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
_tabs.SetTabTitle(TabClientVars, "Client Variables");
|
||||
|
||||
var first = true;
|
||||
foreach (var group in LocalPropertyList(obj, ViewVariablesManager, _resourceCache))
|
||||
foreach (var group in LocalPropertyList(obj, ViewVariablesManager, _robustSerializer))
|
||||
{
|
||||
ViewVariablesTraitMembers.CreateMemberGroupHeader(
|
||||
ref first,
|
||||
@@ -202,7 +203,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
button.Visible = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
button.Visible = true;
|
||||
}
|
||||
}
|
||||
@@ -231,7 +232,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
button.Visible = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (!button.Text.Contains(searchStr, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
button.Visible = false;
|
||||
@@ -314,7 +315,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
|
||||
foreach (var propertyData in groupMembers)
|
||||
{
|
||||
var propertyEdit = new ViewVariablesPropertyControl(ViewVariablesManager, _resourceCache);
|
||||
var propertyEdit = new ViewVariablesPropertyControl(ViewVariablesManager, _robustSerializer);
|
||||
propertyEdit.SetStyle(otherStyle = !otherStyle);
|
||||
var editor = propertyEdit.SetProperty(propertyData);
|
||||
var selectorChain = new object[] {new ViewVariablesMemberSelector(propertyData.PropertyIndex)};
|
||||
@@ -328,7 +329,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
var componentsBlob = await ViewVariablesManager.RequestData<ViewVariablesBlobEntityComponents>(_entitySession, new ViewVariablesRequestEntityComponents());
|
||||
|
||||
_serverComponents.DisposeAllChildren();
|
||||
|
||||
|
||||
_serverComponents.AddChild(_serverComponentsSearchBar = new LineEdit
|
||||
{
|
||||
PlaceHolder = Loc.GetString("Search"),
|
||||
@@ -336,7 +337,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
});
|
||||
|
||||
_serverComponentsSearchBar.OnTextChanged += OnServerComponentsSearchBarChanged;
|
||||
|
||||
|
||||
componentsBlob.ComponentTypes.Sort();
|
||||
|
||||
var componentTypes = componentsBlob.ComponentTypes.AsEnumerable();
|
||||
|
||||
@@ -4,6 +4,7 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.ViewVariables.Traits;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -19,8 +20,8 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
public ViewVariablesRemoteSession? Session { get; private set; }
|
||||
public object? Object { get; private set; }
|
||||
|
||||
public ViewVariablesInstanceObject(IViewVariablesManagerInternal vvm, IResourceCache resCache)
|
||||
: base(vvm, resCache) { }
|
||||
public ViewVariablesInstanceObject(IViewVariablesManagerInternal vvm, IRobustSerializer robustSerializer)
|
||||
: base(vvm, robustSerializer) { }
|
||||
|
||||
public override void Initialize(SS14Window window, object obj)
|
||||
{
|
||||
@@ -113,7 +114,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
var list = new List<ViewVariablesTrait>(traitData.Count);
|
||||
if (traitData.Contains(ViewVariablesTraits.Members))
|
||||
{
|
||||
list.Add(new ViewVariablesTraitMembers(ViewVariablesManager, _resourceCache));
|
||||
list.Add(new ViewVariablesTraitMembers(ViewVariablesManager, _robustSerializer));
|
||||
}
|
||||
|
||||
if (traitData.Contains(ViewVariablesTraits.Enumerable))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.ViewVariables.Instances;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -11,7 +12,7 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
internal class ViewVariablesTraitMembers : ViewVariablesTrait
|
||||
{
|
||||
private readonly IViewVariablesManagerInternal _vvm;
|
||||
private readonly IResourceCache _resourceCache;
|
||||
private readonly IRobustSerializer _robustSerializer;
|
||||
|
||||
private VBoxContainer _memberList = default!;
|
||||
|
||||
@@ -22,9 +23,9 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
instance.AddTab("Members", _memberList);
|
||||
}
|
||||
|
||||
public ViewVariablesTraitMembers(IViewVariablesManagerInternal vvm, IResourceCache resourceCache)
|
||||
public ViewVariablesTraitMembers(IViewVariablesManagerInternal vvm, IRobustSerializer robustSerializer)
|
||||
{
|
||||
_resourceCache = resourceCache;
|
||||
_robustSerializer = robustSerializer;
|
||||
_vvm = vvm;
|
||||
}
|
||||
|
||||
@@ -36,7 +37,7 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
{
|
||||
var first = true;
|
||||
foreach (var group in ViewVariablesInstance.LocalPropertyList(Instance.Object,
|
||||
Instance.ViewVariablesManager, _resourceCache))
|
||||
Instance.ViewVariablesManager, _robustSerializer))
|
||||
{
|
||||
CreateMemberGroupHeader(
|
||||
ref first,
|
||||
@@ -64,7 +65,7 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
|
||||
foreach (var propertyData in groupMembers)
|
||||
{
|
||||
var propertyEdit = new ViewVariablesPropertyControl(_vvm, _resourceCache);
|
||||
var propertyEdit = new ViewVariablesPropertyControl(_vvm, _robustSerializer);
|
||||
propertyEdit.SetStyle(otherStyle = !otherStyle);
|
||||
var editor = propertyEdit.SetProperty(propertyData);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.ViewVariables.Traits;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -19,12 +20,12 @@ namespace Robust.Client.ViewVariables
|
||||
internal abstract class ViewVariablesInstance
|
||||
{
|
||||
public readonly IViewVariablesManagerInternal ViewVariablesManager;
|
||||
protected readonly IResourceCache _resourceCache;
|
||||
protected readonly IRobustSerializer _robustSerializer;
|
||||
|
||||
protected ViewVariablesInstance(IViewVariablesManagerInternal vvm, IResourceCache resCache)
|
||||
protected ViewVariablesInstance(IViewVariablesManagerInternal vvm, IRobustSerializer robustSerializer)
|
||||
{
|
||||
ViewVariablesManager = vvm;
|
||||
_resourceCache = resCache;
|
||||
_robustSerializer = robustSerializer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -54,7 +55,7 @@ namespace Robust.Client.ViewVariables
|
||||
}
|
||||
|
||||
protected internal static IEnumerable<IGrouping<Type, Control>> LocalPropertyList(object obj, IViewVariablesManagerInternal vvm,
|
||||
IResourceCache resCache)
|
||||
IRobustSerializer robustSerializer)
|
||||
{
|
||||
var styleOther = false;
|
||||
var type = obj.GetType();
|
||||
@@ -104,7 +105,7 @@ namespace Robust.Client.ViewVariables
|
||||
Value = value
|
||||
};
|
||||
|
||||
var propertyEdit = new ViewVariablesPropertyControl(vvm, resCache);
|
||||
var propertyEdit = new ViewVariablesPropertyControl(vvm, robustSerializer);
|
||||
propertyEdit.SetStyle(styleOther = !styleOther);
|
||||
var editor = propertyEdit.SetProperty(data);
|
||||
editor.OnValueChanged += onValueChanged;
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Robust.Client.ViewVariables
|
||||
internal class ViewVariablesManager : ViewVariablesManagerShared, IViewVariablesManagerInternal
|
||||
{
|
||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IRobustSerializer _robustSerializer = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private uint _nextReqId = 1;
|
||||
@@ -214,11 +214,11 @@ namespace Robust.Client.ViewVariables
|
||||
ViewVariablesInstance instance;
|
||||
if (obj is IEntity entity && !entity.Deleted)
|
||||
{
|
||||
instance = new ViewVariablesInstanceEntity(this, _resourceCache, _entityManager);
|
||||
instance = new ViewVariablesInstanceEntity(this, _entityManager, _robustSerializer);
|
||||
}
|
||||
else
|
||||
{
|
||||
instance = new ViewVariablesInstanceObject(this, _resourceCache);
|
||||
instance = new ViewVariablesInstanceObject(this, _robustSerializer);
|
||||
}
|
||||
|
||||
var window = new SS14Window {Title = "View Variables"};
|
||||
@@ -255,11 +255,11 @@ namespace Robust.Client.ViewVariables
|
||||
ViewVariablesInstance instance;
|
||||
if (type != null && typeof(IEntity).IsAssignableFrom(type))
|
||||
{
|
||||
instance = new ViewVariablesInstanceEntity(this, _resourceCache, _entityManager);
|
||||
instance = new ViewVariablesInstanceEntity(this, _entityManager, _robustSerializer);
|
||||
}
|
||||
else
|
||||
{
|
||||
instance = new ViewVariablesInstanceObject(this, _resourceCache);
|
||||
instance = new ViewVariablesInstanceObject(this, _robustSerializer);
|
||||
}
|
||||
|
||||
loadingLabel.Dispose();
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.ViewVariables.Editors;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -23,14 +24,14 @@ namespace Robust.Client.ViewVariables
|
||||
private readonly Label _bottomLabel;
|
||||
|
||||
private readonly IViewVariablesManagerInternal _viewVariablesManager;
|
||||
private readonly IResourceCache _resourceCache;
|
||||
private readonly IRobustSerializer _robustSerializer;
|
||||
|
||||
public ViewVariablesPropertyControl(IViewVariablesManagerInternal viewVars, IResourceCache resourceCache)
|
||||
public ViewVariablesPropertyControl(IViewVariablesManagerInternal viewVars, IRobustSerializer robustSerializer)
|
||||
{
|
||||
MouseFilter = MouseFilterMode.Pass;
|
||||
|
||||
_viewVariablesManager = viewVars;
|
||||
_resourceCache = resourceCache;
|
||||
_robustSerializer = robustSerializer;
|
||||
|
||||
MouseFilter = MouseFilterMode.Pass;
|
||||
ToolTip = "Click to expand";
|
||||
@@ -68,11 +69,11 @@ namespace Robust.Client.ViewVariables
|
||||
|
||||
_bottomLabel.Text = $"Type: {member.TypePretty}";
|
||||
VVPropEditor editor;
|
||||
if (type == null)
|
||||
if (type == null || !_robustSerializer.CanSerialize(type))
|
||||
{
|
||||
// Type is server-side only.
|
||||
// Info whether it's reference or value type can be figured out from the sent value.
|
||||
if (member.Value is ViewVariablesBlobMembers.ServerValueTypeToken)
|
||||
if (type?.IsValueType == true || member.Value is ViewVariablesBlobMembers.ServerValueTypeToken)
|
||||
{
|
||||
// Value type, just display it stringified read-only.
|
||||
editor = new VVPropEditorDummy();
|
||||
@@ -80,7 +81,7 @@ namespace Robust.Client.ViewVariables
|
||||
else
|
||||
{
|
||||
// Has to be a reference type at this point.
|
||||
DebugTools.Assert(member.Value is ViewVariablesBlobMembers.ReferenceToken || member.Value == null);
|
||||
DebugTools.Assert(member.Value is ViewVariablesBlobMembers.ReferenceToken || member.Value == null || type?.IsClass == true || type?.IsInterface == true);
|
||||
editor = _viewVariablesManager.PropertyFor(typeof(object));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Prometheus;
|
||||
using Robust.Server.Console;
|
||||
@@ -37,6 +38,7 @@ using Robust.Shared;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Server.DataMetrics;
|
||||
using Robust.Server.Log;
|
||||
using Robust.Server.Utility;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Serilog.Debugging;
|
||||
@@ -286,6 +288,8 @@ namespace Robust.Server
|
||||
|
||||
// Initialize Tier 2 services
|
||||
IoCManager.Resolve<IGameTiming>().InSimulation = true;
|
||||
|
||||
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
|
||||
|
||||
_stateManager.Initialize();
|
||||
IoCManager.Resolve<IPlayerManager>().Initialize(MaxPlayers);
|
||||
@@ -320,6 +324,11 @@ namespace Robust.Server
|
||||
|
||||
_stringSerializer.LockStrings();
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && _config.GetCVar(CVars.SysWinTickPeriod) >= 0)
|
||||
{
|
||||
WindowsTickPeriod.TimeBeginPeriod((uint) _config.GetCVar(CVars.SysWinTickPeriod));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -480,6 +489,8 @@ namespace Robust.Server
|
||||
// called right before main loop returns, do all saving/cleanup in here
|
||||
private void Cleanup()
|
||||
{
|
||||
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
|
||||
|
||||
// shut down networking, kicking all players.
|
||||
_network.Shutdown($"Server shutting down: {_shutdownReason}");
|
||||
|
||||
@@ -500,6 +511,11 @@ namespace Robust.Server
|
||||
AppDomain.CurrentDomain.ProcessExit -= ProcessExiting;
|
||||
|
||||
//TODO: This should prob shutdown all managers in a loop.
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && _config.GetCVar(CVars.SysWinTickPeriod) >= 0)
|
||||
{
|
||||
WindowsTickPeriod.TimeEndPeriod((uint) _config.GetCVar(CVars.SysWinTickPeriod));
|
||||
}
|
||||
}
|
||||
|
||||
private string UpdateBps()
|
||||
@@ -528,6 +544,9 @@ namespace Robust.Server
|
||||
ServerCurTick.Set(_time.CurTick.Value);
|
||||
ServerCurTime.Set(_time.CurTime.TotalSeconds);
|
||||
|
||||
// These are always the same on the server, there is no prediction.
|
||||
_time.LastRealTick = _time.CurTick;
|
||||
|
||||
UpdateTitle();
|
||||
|
||||
using (TickUsage.WithLabels("PreEngine").NewTimer())
|
||||
@@ -535,6 +554,11 @@ namespace Robust.Server
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.PreEngine, frameEventArgs);
|
||||
}
|
||||
|
||||
using (TickUsage.WithLabels("NetworkedCVar").NewTimer())
|
||||
{
|
||||
IoCManager.Resolve<INetConfigurationManager>().TickProcessMessages();
|
||||
}
|
||||
|
||||
using (TickUsage.WithLabels("Timers").NewTimer())
|
||||
{
|
||||
timerManager.UpdateTimers(frameEventArgs);
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace Robust.Server.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
internal sealed class RemoveCompCommand : IClientCommand
|
||||
{
|
||||
public string Command => "rmcomp";
|
||||
|
||||
@@ -40,17 +40,17 @@ namespace Robust.Server.GameObjects
|
||||
return (T)data[key];
|
||||
}
|
||||
|
||||
public override bool TryGetData<T>(Enum key, [MaybeNullWhen(false)] out T data)
|
||||
public override bool TryGetData<T>(Enum key, [NotNullWhen(true)] out T data)
|
||||
{
|
||||
return TryGetData(key, out data);
|
||||
}
|
||||
|
||||
public override bool TryGetData<T>(string key, [MaybeNullWhen(false)] out T data)
|
||||
public override bool TryGetData<T>(string key, [NotNullWhen(true)] out T data)
|
||||
{
|
||||
return TryGetData(key, out data);
|
||||
}
|
||||
|
||||
bool TryGetData<T>(object key, [MaybeNullWhen(false)] out T data)
|
||||
private bool TryGetData<T>(object key, [NotNullWhen(true)] out T data)
|
||||
{
|
||||
if (this.data.TryGetValue(key, out var dat))
|
||||
{
|
||||
|
||||
@@ -32,8 +32,11 @@ namespace Robust.Server.GameObjects
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
_enabled = value;
|
||||
Dirty();
|
||||
if (_enabled != value)
|
||||
{
|
||||
_enabled = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Robust.Server.Interfaces.ServerStatus
|
||||
{
|
||||
public delegate bool StatusHostHandler(
|
||||
HttpMethod method,
|
||||
HttpListenerRequest request,
|
||||
HttpListenerResponse response);
|
||||
IStatusHandlerContext context);
|
||||
|
||||
public interface IStatusHost
|
||||
{
|
||||
@@ -32,4 +33,30 @@ namespace Robust.Server.Interfaces.ServerStatus
|
||||
/// </summary>
|
||||
event Action<JObject> OnInfoRequest;
|
||||
}
|
||||
|
||||
public interface IStatusHandlerContext
|
||||
{
|
||||
HttpMethod RequestMethod { get; }
|
||||
IPEndPoint RemoteEndPoint { get; }
|
||||
Uri Url { get; }
|
||||
bool IsGetLike { get; }
|
||||
IReadOnlyDictionary<string, StringValues> RequestHeaders { get; }
|
||||
|
||||
[return: MaybeNull]
|
||||
public T RequestBodyJson<T>();
|
||||
|
||||
void Respond(
|
||||
string text,
|
||||
HttpStatusCode code = HttpStatusCode.OK,
|
||||
string contentType = "text/plain");
|
||||
|
||||
void Respond(
|
||||
string text,
|
||||
int code = 200,
|
||||
string contentType = "text/plain");
|
||||
|
||||
void RespondError(HttpStatusCode code);
|
||||
|
||||
void RespondJson(object jsonData, HttpStatusCode code = HttpStatusCode.OK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Linq;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network.Messages;
|
||||
|
||||
@@ -88,6 +89,12 @@ namespace Robust.Server.Placement
|
||||
|
||||
var coordinates = msg.EntityCoordinates;
|
||||
|
||||
if (!coordinates.IsValid(_entityManager))
|
||||
{
|
||||
Logger.WarningS("placement",
|
||||
$"{session} tried to place {msg.ObjType} at invalid coordinate {coordinates}");
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO: Redesign permission system, or document what this is supposed to be doing
|
||||
var permission = GetPermission(session.attachedEntity.Uid, alignRcv);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
||||
using Prometheus;
|
||||
using Robust.Server.Interfaces;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Input;
|
||||
@@ -91,8 +92,6 @@ namespace Robust.Server.Player
|
||||
|
||||
MaxPlayers = maxPlayers;
|
||||
|
||||
_network.RegisterNetMessage<MsgServerInfoReq>(MsgServerInfoReq.NAME, HandleWelcomeMessageReq);
|
||||
_network.RegisterNetMessage<MsgServerInfo>(MsgServerInfo.NAME);
|
||||
_network.RegisterNetMessage<MsgPlayerListReq>(MsgPlayerListReq.NAME, HandlePlayerListReq);
|
||||
_network.RegisterNetMessage<MsgPlayerList>(MsgPlayerList.NAME);
|
||||
|
||||
@@ -378,6 +377,8 @@ namespace Robust.Server.Player
|
||||
}
|
||||
|
||||
PlayerCountMetric.Set(PlayerCount);
|
||||
|
||||
IoCManager.Resolve<INetConfigurationManager>().SyncConnectingClient(args.Channel);
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(IPlayerSession session, SessionStatus oldStatus, SessionStatus newStatus)
|
||||
@@ -414,18 +415,6 @@ namespace Robust.Server.Player
|
||||
Dirty();
|
||||
}
|
||||
|
||||
private void HandleWelcomeMessageReq(MsgServerInfoReq message)
|
||||
{
|
||||
var channel = message.MsgChannel;
|
||||
var netMsg = channel.CreateNetMessage<MsgServerInfo>();
|
||||
|
||||
netMsg.ServerName = _baseServer.ServerName;
|
||||
netMsg.ServerMaxPlayers = _baseServer.MaxPlayers;
|
||||
netMsg.TickRate = _timing.TickRate;
|
||||
|
||||
channel.SendMessage(netMsg);
|
||||
}
|
||||
|
||||
private void HandlePlayerListReq(MsgPlayerListReq message)
|
||||
{
|
||||
var channel = message.MsgChannel;
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="5.0.0" />
|
||||
<PackageReference Include="prometheus-net" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Loki" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Primitives" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Lidgren.Network\Lidgren.Network.csproj" />
|
||||
<ProjectReference Include="..\ManagedHttpListener\src\System.Net.HttpListener.csproj" />
|
||||
<ProjectReference Include="..\Robust.Physics\Robust.Physics.csproj" />
|
||||
<ProjectReference Include="..\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
|
||||
<ProjectReference Include="..\Robust.Shared.Scripting\Robust.Shared.Scripting.csproj" />
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Robust.Server.Interfaces.ServerStatus;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.ServerStatus
|
||||
{
|
||||
@@ -20,69 +17,46 @@ namespace Robust.Server.ServerStatus
|
||||
AddHandler(HandleInfo);
|
||||
}
|
||||
|
||||
private static bool HandleTeapot(HttpMethod method, HttpListenerRequest request, HttpListenerResponse response)
|
||||
private static bool HandleTeapot(IStatusHandlerContext context)
|
||||
{
|
||||
if (!method.IsGetLike() || request.Url!.AbsolutePath != "/teapot")
|
||||
if (!context.IsGetLike || context.Url!.AbsolutePath != "/teapot")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
response.Respond(method, "I am a teapot.", (HttpStatusCode) 418);
|
||||
context.Respond("I am a teapot.", (HttpStatusCode) 418);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleStatus(HttpMethod method, HttpListenerRequest request, HttpListenerResponse response)
|
||||
private bool HandleStatus(IStatusHandlerContext context)
|
||||
{
|
||||
if (!method.IsGetLike() || request.Url!.AbsolutePath != "/status")
|
||||
if (!context.IsGetLike || context.Url!.AbsolutePath != "/status")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (OnStatusRequest == null)
|
||||
var jObject = new JObject
|
||||
{
|
||||
_httpSawmill.Warning("OnStatusRequest is not set, responding with a 501.");
|
||||
response.Respond(method, "Not Implemented", HttpStatusCode.NotImplemented);
|
||||
return true;
|
||||
}
|
||||
|
||||
response.StatusCode = (int) HttpStatusCode.OK;
|
||||
response.ContentType = "application/json";
|
||||
|
||||
if (method == HttpMethod.Head)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var jObject = new JObject();
|
||||
// We need to send at LEAST name and player count to have the launcher work with us.
|
||||
// Content can override these if it wants (e.g. stealthmins).
|
||||
["name"] = _serverNameCache,
|
||||
["players"] = _playerManager.PlayerCount
|
||||
};
|
||||
|
||||
OnStatusRequest?.Invoke(jObject);
|
||||
|
||||
using var streamWriter = new StreamWriter(response.OutputStream, EncodingHelpers.UTF8);
|
||||
|
||||
using var jsonWriter = new JsonTextWriter(streamWriter);
|
||||
|
||||
JsonSerializer.Serialize(jsonWriter, jObject);
|
||||
|
||||
jsonWriter.Flush();
|
||||
context.RespondJson(jObject);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleInfo(HttpMethod method, HttpListenerRequest request, HttpListenerResponse response)
|
||||
private bool HandleInfo(IStatusHandlerContext context)
|
||||
{
|
||||
if (!method.IsGetLike() || request.Url!.AbsolutePath != "/info")
|
||||
if (!context.IsGetLike || context.Url!.AbsolutePath != "/info")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
response.StatusCode = (int) HttpStatusCode.OK;
|
||||
response.ContentType = "application/json";
|
||||
|
||||
if (method == HttpMethod.Head)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var downloadUrl = _configurationManager.GetCVar(CVars.BuildDownloadUrl);
|
||||
|
||||
JObject? buildInfo;
|
||||
@@ -126,13 +100,7 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
OnInfoRequest?.Invoke(jObject);
|
||||
|
||||
using var streamWriter = new StreamWriter(response.OutputStream, EncodingHelpers.UTF8);
|
||||
|
||||
using var jsonWriter = new JsonTextWriter(streamWriter);
|
||||
|
||||
JsonSerializer.Serialize(jsonWriter, jObject);
|
||||
|
||||
jsonWriter.Flush();
|
||||
context.RespondJson(jObject);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Server.Interfaces.ServerStatus;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Interfaces.Log;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
using HttpListener = ManagedHttpListener.HttpListener;
|
||||
using HttpListenerContext = ManagedHttpListener.HttpListenerContext;
|
||||
|
||||
// This entire file is NIHing a REST server because pulling in libraries is effort.
|
||||
// Also it was fun to write.
|
||||
@@ -28,6 +36,7 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IServerNetManager _netManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
private static readonly JsonSerializer JsonSerializer = new();
|
||||
private readonly List<StatusHostHandler> _handlers = new();
|
||||
@@ -35,19 +44,20 @@ namespace Robust.Server.ServerStatus
|
||||
private TaskCompletionSource? _stopSource;
|
||||
private ISawmill _httpSawmill = default!;
|
||||
|
||||
private string? _serverNameCache;
|
||||
|
||||
public Task ProcessRequestAsync(HttpListenerContext context)
|
||||
{
|
||||
var response = context.Response;
|
||||
var request = context.Request;
|
||||
var method = new HttpMethod(request.HttpMethod);
|
||||
var apiContext = (IStatusHandlerContext) new ContextImpl(context);
|
||||
|
||||
_httpSawmill.Info($"{method} {context.Request.Url?.PathAndQuery} from {request.RemoteEndPoint}");
|
||||
_httpSawmill.Info(
|
||||
$"{apiContext.RequestMethod} {apiContext.Url.PathAndQuery} from {apiContext.RemoteEndPoint}");
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var handler in _handlers)
|
||||
{
|
||||
if (handler(method, request, response))
|
||||
if (handler(apiContext))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -55,12 +65,12 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
// No handler returned true, assume no handlers care about this.
|
||||
// 404.
|
||||
response.Respond(method, "Not Found", HttpStatusCode.NotFound);
|
||||
apiContext.Respond("Not Found", HttpStatusCode.NotFound);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
response.Respond(method, "Internal Server Error", HttpStatusCode.InternalServerError);
|
||||
_httpSawmill.Error($"Exception in StatusHost: {e}");
|
||||
apiContext.Respond("Internal Server Error", HttpStatusCode.InternalServerError);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -85,6 +95,10 @@ namespace Robust.Server.ServerStatus
|
||||
_httpSawmill = Logger.GetSawmill($"{Sawmill}.http");
|
||||
RegisterCVars();
|
||||
|
||||
// Cache this in a field to avoid thread safety shenanigans.
|
||||
// Writes/reads of references are atomic in C# so no further synchronization necessary.
|
||||
_configurationManager.OnValueChanged(CVars.GameHostName, n => _serverNameCache = n);
|
||||
|
||||
if (!_configurationManager.GetCVar(CVars.StatusEnabled))
|
||||
{
|
||||
return;
|
||||
@@ -146,15 +160,22 @@ namespace Robust.Server.ServerStatus
|
||||
var buildInfo = File.ReadAllText(PathHelpers.ExecutableRelativeFile("build.json"));
|
||||
var info = JsonConvert.DeserializeObject<BuildInfo>(buildInfo);
|
||||
|
||||
_configurationManager.SetCVar(CVars.BuildEngineVersion, info.EngineVersion);
|
||||
_configurationManager.SetCVar(CVars.BuildForkId, info.ForkId);
|
||||
_configurationManager.SetCVar(CVars.BuildVersion, info.Version);
|
||||
_configurationManager.SetCVar(CVars.BuildDownloadUrl, info.Download);
|
||||
_configurationManager.SetCVar(CVars.BuildHash, info.Hash ?? "");
|
||||
// Don't replace cvars with contents of build.json if overriden by --cvar or such.
|
||||
SetCVarIfUnmodified(CVars.BuildEngineVersion, info.EngineVersion);
|
||||
SetCVarIfUnmodified(CVars.BuildForkId, info.ForkId);
|
||||
SetCVarIfUnmodified(CVars.BuildVersion, info.Version);
|
||||
SetCVarIfUnmodified(CVars.BuildDownloadUrl, info.Download ?? "");
|
||||
SetCVarIfUnmodified(CVars.BuildHash, info.Hash ?? "");
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
}
|
||||
|
||||
void SetCVarIfUnmodified(CVarDef<string> cvar, string val)
|
||||
{
|
||||
if (_configurationManager.GetCVar(cvar) == "")
|
||||
_configurationManager.SetCVar(cvar, val);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -168,14 +189,91 @@ namespace Robust.Server.ServerStatus
|
||||
_listener!.Stop();
|
||||
}
|
||||
|
||||
#pragma warning disable CS0649
|
||||
[JsonObject(ItemRequired = Required.DisallowNull)]
|
||||
private sealed class BuildInfo
|
||||
{
|
||||
[JsonProperty("engine_version")] public string EngineVersion = default!;
|
||||
[JsonProperty("hash")] public string? Hash;
|
||||
[JsonProperty("download")] public string Download = default!;
|
||||
[JsonProperty("download")] public string? Download = default;
|
||||
[JsonProperty("fork_id")] public string ForkId = default!;
|
||||
[JsonProperty("version")] public string Version = default!;
|
||||
}
|
||||
#pragma warning restore CS0649
|
||||
|
||||
private sealed class ContextImpl : IStatusHandlerContext
|
||||
{
|
||||
private readonly HttpListenerContext _context;
|
||||
public HttpMethod RequestMethod { get; }
|
||||
public IPEndPoint RemoteEndPoint => _context.Request.RemoteEndPoint!;
|
||||
public Uri Url => _context.Request.Url!;
|
||||
public bool IsGetLike => RequestMethod == HttpMethod.Head || RequestMethod == HttpMethod.Get;
|
||||
public IReadOnlyDictionary<string, StringValues> RequestHeaders { get; }
|
||||
|
||||
public ContextImpl(HttpListenerContext context)
|
||||
{
|
||||
_context = context;
|
||||
RequestMethod = new HttpMethod(context.Request.HttpMethod!);
|
||||
|
||||
var headers = new Dictionary<string, StringValues>();
|
||||
foreach (string? key in context.Request.Headers.Keys)
|
||||
{
|
||||
if (key == null)
|
||||
continue;
|
||||
|
||||
headers.Add(key, context.Request.Headers.GetValues(key));
|
||||
}
|
||||
|
||||
RequestHeaders = headers;
|
||||
}
|
||||
|
||||
[return: MaybeNull]
|
||||
public T RequestBodyJson<T>()
|
||||
{
|
||||
using var streamReader = new StreamReader(_context.Request.InputStream, EncodingHelpers.UTF8);
|
||||
using var jsonReader = new JsonTextReader(streamReader);
|
||||
|
||||
var serializer = new JsonSerializer();
|
||||
return serializer.Deserialize<T>(jsonReader);
|
||||
}
|
||||
|
||||
public void Respond(string text, HttpStatusCode code = HttpStatusCode.OK, string contentType = MediaTypeNames.Text.Plain)
|
||||
{
|
||||
Respond(text, (int) code, contentType);
|
||||
}
|
||||
|
||||
public void Respond(string text, int code = 200, string contentType = MediaTypeNames.Text.Plain)
|
||||
{
|
||||
_context.Response.StatusCode = code;
|
||||
_context.Response.ContentType = contentType;
|
||||
|
||||
if (RequestMethod == HttpMethod.Head)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var writer = new StreamWriter(_context.Response.OutputStream, EncodingHelpers.UTF8);
|
||||
|
||||
writer.Write(text);
|
||||
}
|
||||
|
||||
public void RespondError(HttpStatusCode code)
|
||||
{
|
||||
Respond(code.ToString(), code);
|
||||
}
|
||||
|
||||
public void RespondJson(object jsonData, HttpStatusCode code = HttpStatusCode.OK)
|
||||
{
|
||||
using var streamWriter = new StreamWriter(_context.Response.OutputStream, EncodingHelpers.UTF8);
|
||||
|
||||
_context.Response.ContentType = MediaTypeNames.Application.Json;
|
||||
|
||||
using var jsonWriter = new JsonTextWriter(streamWriter);
|
||||
|
||||
JsonSerializer.Serialize(jsonWriter, jsonData);
|
||||
|
||||
jsonWriter.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -9,41 +8,6 @@ namespace Robust.Server.ServerStatus
|
||||
{
|
||||
public static class StatusHostHelpers
|
||||
{
|
||||
public static bool IsGetLike(this HttpMethod method)
|
||||
{
|
||||
return method == HttpMethod.Get || method == HttpMethod.Head;
|
||||
}
|
||||
|
||||
public static void Respond(
|
||||
this HttpListenerResponse response,
|
||||
HttpMethod method,
|
||||
string text,
|
||||
HttpStatusCode code = HttpStatusCode.OK,
|
||||
string contentType = "text/plain")
|
||||
{
|
||||
response.Respond(method, text, (int) code, contentType);
|
||||
}
|
||||
|
||||
public static void Respond(
|
||||
this HttpListenerResponse response,
|
||||
HttpMethod method,
|
||||
string text,
|
||||
int code = 200,
|
||||
string contentType = "text/plain")
|
||||
{
|
||||
response.StatusCode = code;
|
||||
response.ContentType = contentType;
|
||||
|
||||
if (method == HttpMethod.Head)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var writer = new StreamWriter(response.OutputStream, EncodingHelpers.UTF8);
|
||||
|
||||
writer.Write(text);
|
||||
}
|
||||
|
||||
[return: MaybeNull]
|
||||
public static T GetFromJson<T>(this HttpListenerRequest request)
|
||||
{
|
||||
|
||||
@@ -45,9 +45,9 @@ namespace Robust.Server.ServerStatus
|
||||
_statusHost.AddHandler(ShutdownHandler);
|
||||
}
|
||||
|
||||
private bool UpdateHandler(HttpMethod method, HttpListenerRequest request, HttpListenerResponse response)
|
||||
private bool UpdateHandler(IStatusHandlerContext context)
|
||||
{
|
||||
if (method != HttpMethod.Post || request.Url!.AbsolutePath != "/update")
|
||||
if (context.RequestMethod != HttpMethod.Post || context.Url!.AbsolutePath != "/update")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -58,26 +58,26 @@ namespace Robust.Server.ServerStatus
|
||||
return false;
|
||||
}
|
||||
|
||||
var auth = request.Headers["WatchdogToken"];
|
||||
var auth = context.RequestHeaders["WatchdogToken"];
|
||||
|
||||
if (auth != _watchdogToken)
|
||||
{
|
||||
// Holy shit nobody read these logs please.
|
||||
_sawmill.Info(@"Failed auth: ""{0}"" vs ""{1}""", auth, _watchdogToken);
|
||||
response.StatusCode = (int) HttpStatusCode.Unauthorized;
|
||||
context.RespondError(HttpStatusCode.Unauthorized);
|
||||
return true;
|
||||
}
|
||||
|
||||
_taskManager.RunOnMainThread(() => UpdateReceived?.Invoke());
|
||||
|
||||
response.StatusCode = (int) HttpStatusCode.OK;
|
||||
context.Respond("Success", HttpStatusCode.OK);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ShutdownHandler(HttpMethod method, HttpListenerRequest request, HttpListenerResponse response)
|
||||
private bool ShutdownHandler(IStatusHandlerContext context)
|
||||
{
|
||||
if (method != HttpMethod.Post || request.Url!.AbsolutePath != "/shutdown")
|
||||
if (context.RequestMethod != HttpMethod.Post || context.Url!.AbsolutePath != "/shutdown")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -88,22 +88,21 @@ namespace Robust.Server.ServerStatus
|
||||
return false;
|
||||
}
|
||||
|
||||
var auth = request.Headers["WatchdogToken"];
|
||||
var auth = context.RequestHeaders["WatchdogToken"];
|
||||
|
||||
if (auth != _watchdogToken)
|
||||
{
|
||||
_sawmill.Warning(
|
||||
"received POST /shutdown with invalid authentication token. Ignoring {0}, {1}", auth,
|
||||
_watchdogToken);
|
||||
response.StatusCode = (int) HttpStatusCode.Unauthorized;
|
||||
context.RespondError(HttpStatusCode.Unauthorized);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
ShutdownParameters? parameters = null;
|
||||
try
|
||||
{
|
||||
parameters = request.GetFromJson<ShutdownParameters>();
|
||||
parameters = context.RequestBodyJson<ShutdownParameters>();
|
||||
}
|
||||
catch (JsonSerializationException)
|
||||
{
|
||||
@@ -112,14 +111,14 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
if (parameters == null)
|
||||
{
|
||||
response.StatusCode = (int) HttpStatusCode.BadRequest;
|
||||
context.RespondError(HttpStatusCode.BadRequest);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_taskManager.RunOnMainThread(() => _baseServer.Shutdown(parameters.Reason));
|
||||
|
||||
response.StatusCode = (int) HttpStatusCode.OK;
|
||||
context.Respond("Success", HttpStatusCode.OK);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
38
Robust.Server/Utility/WindowsTickPeriod.cs
Normal file
38
Robust.Server/Utility/WindowsTickPeriod.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Robust.Server.Utility
|
||||
{
|
||||
internal static class WindowsTickPeriod
|
||||
{
|
||||
private const uint TIMERR_NOERROR = 0;
|
||||
// This is an actual error code my god.
|
||||
private const uint TIMERR_NOCANDO = 97;
|
||||
|
||||
public static void TimeBeginPeriod(uint period)
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
throw new InvalidOperationException();
|
||||
|
||||
var ret = timeBeginPeriod(period);
|
||||
if (ret != TIMERR_NOERROR)
|
||||
throw new InvalidOperationException($"timeBeginPeriod returned error: {ret}");
|
||||
}
|
||||
|
||||
public static void TimeEndPeriod(uint period)
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
throw new InvalidOperationException();
|
||||
|
||||
var ret = timeEndPeriod(period);
|
||||
if (ret != TIMERR_NOERROR)
|
||||
throw new InvalidOperationException($"timeEndPeriod returned error: {ret}");
|
||||
}
|
||||
|
||||
[DllImport("Winmm.dll")]
|
||||
private static extern uint timeBeginPeriod(uint uPeriod);
|
||||
|
||||
[DllImport("Winmm.dll")]
|
||||
private static extern uint timeEndPeriod(uint uPeriod);
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ namespace Robust.Shared.Scripting
|
||||
{
|
||||
return new EntityCoordinates(EntityUid.Invalid, ((float) x, (float) y));
|
||||
}
|
||||
|
||||
|
||||
return new EntityCoordinates(grid.GridEntityId, ((float) x, (float) y));
|
||||
}
|
||||
|
||||
@@ -52,6 +52,11 @@ namespace Robust.Shared.Scripting
|
||||
return getent(eid(i));
|
||||
}
|
||||
|
||||
public T gcm<T>(int i)
|
||||
{
|
||||
return getent(i).GetComponent<T>();
|
||||
}
|
||||
|
||||
public IEntity getent(EntityUid uid)
|
||||
{
|
||||
return ent.GetEntity(uid);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
@@ -59,8 +60,19 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<bool> NetPredict =
|
||||
CVarDef.Create("net.predict", true, CVar.ARCHIVE);
|
||||
|
||||
public static readonly CVarDef<int> NetPredictSize =
|
||||
CVarDef.Create("net.predict_size", 1, CVar.ARCHIVE);
|
||||
public static readonly CVarDef<int> NetPredictTickBias =
|
||||
CVarDef.Create("net.predict_tick_bias", 1, CVar.ARCHIVE);
|
||||
|
||||
// On Windows we default this to 16ms lag bias, to account for time period lag in the Lidgren thread.
|
||||
// Basically due to how time periods work on Windows, messages are (at worst) time period-delayed when sending.
|
||||
// BUT! Lidgren's latency calculation *never* measures this due to how it works.
|
||||
// This broke some prediction calculations quite badly so we bias them to mask it.
|
||||
// This is not necessary on Linux because Linux, for better or worse,
|
||||
// just has the Lidgren thread go absolute brr polling.
|
||||
public static readonly CVarDef<float> NetPredictLagBias = CVarDef.Create(
|
||||
"net.predict_lag_bias",
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 0.016f : 0,
|
||||
CVar.ARCHIVE);
|
||||
|
||||
public static readonly CVarDef<int> NetStateBufMergeThreshold =
|
||||
CVarDef.Create("net.state_buf_merge_threshold", 5, CVar.ARCHIVE);
|
||||
@@ -77,6 +89,8 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<int> NetTickrate =
|
||||
CVarDef.Create("net.tickrate", 60, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
public static readonly CVarDef<int> SysWinTickPeriod =
|
||||
CVarDef.Create("sys.win_tick_period", 3, CVar.SERVERONLY);
|
||||
|
||||
#if DEBUG
|
||||
public static readonly CVarDef<float> NetFakeLoss = CVarDef.Create("net.fakeloss", 0f, CVar.CHEAT);
|
||||
@@ -151,10 +165,10 @@ namespace Robust.Shared
|
||||
*/
|
||||
|
||||
public static readonly CVarDef<int> GameMaxPlayers =
|
||||
CVarDef.Create("game.maxplayers", 32, CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
CVarDef.Create("game.maxplayers", 32, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
public static readonly CVarDef<string> GameHostName =
|
||||
CVarDef.Create("game.hostname", "MyServer", CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
CVarDef.Create("game.hostname", "MyServer", CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
/*
|
||||
* LOG
|
||||
@@ -233,10 +247,10 @@ namespace Robust.Shared
|
||||
CVarDef.Create("display.height", 720, CVar.CLIENTONLY);
|
||||
|
||||
public static readonly CVarDef<int> DisplayLightMapDivider =
|
||||
CVarDef.Create("display.lightmapdivider", 2, CVar.CLIENTONLY);
|
||||
CVarDef.Create("display.lightmapdivider", 2, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
public static readonly CVarDef<bool> DisplaySoftShadows =
|
||||
CVarDef.Create("display.softshadows", true, CVar.CLIENTONLY);
|
||||
CVarDef.Create("display.softshadows", true, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
public static readonly CVarDef<float> DisplayUIScale =
|
||||
CVarDef.Create("display.uiScale", 0f, CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
@@ -261,7 +275,7 @@ namespace Robust.Shared
|
||||
CVarDef.Create("audio.device", string.Empty, CVar.CLIENTONLY);
|
||||
|
||||
public static readonly CVarDef<float> AudioMasterVolume =
|
||||
CVarDef.Create("audio.mastervolume", 1.0f, CVar.CLIENTONLY);
|
||||
CVarDef.Create("audio.mastervolume", 1.0f, CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
|
||||
/*
|
||||
* PLAYER
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Nett;
|
||||
using Nett;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Log;
|
||||
using System;
|
||||
@@ -13,12 +13,12 @@ namespace Robust.Shared.Configuration
|
||||
/// <summary>
|
||||
/// Stores and manages global configuration variables.
|
||||
/// </summary>
|
||||
internal sealed class ConfigurationManager : IConfigurationManagerInternal
|
||||
internal class ConfigurationManager : IConfigurationManagerInternal
|
||||
{
|
||||
private const char TABLE_DELIMITER = '.';
|
||||
private readonly Dictionary<string, ConfigVar> _configVars = new();
|
||||
protected readonly Dictionary<string, ConfigVar> _configVars = new();
|
||||
private string? _configFile;
|
||||
private bool _isServer;
|
||||
protected bool _isServer;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new ConfigurationManager.
|
||||
@@ -79,6 +79,7 @@ namespace Robust.Shared.Configuration
|
||||
else // this is a key, add CVar
|
||||
{
|
||||
// if the CVar has already been registered
|
||||
var tomlValue = TypeConvert(obj);
|
||||
if (_configVars.TryGetValue(tablePath, out var cfgVar))
|
||||
{
|
||||
if ((cfgVar.Flags & CVar.SECURE) != 0)
|
||||
@@ -90,13 +91,14 @@ namespace Robust.Shared.Configuration
|
||||
return;
|
||||
}
|
||||
// overwrite the value with the saved one
|
||||
cfgVar.Value = TypeConvert(obj);
|
||||
cfgVar.Value = tomlValue;
|
||||
cfgVar.ValueChanged?.Invoke(cfgVar.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
//or add another unregistered CVar
|
||||
cfgVar = new ConfigVar(tablePath, null, CVar.NONE) { Value = TypeConvert(obj) };
|
||||
//Note: the defaultValue is arbitrarily 0, it will get overwritten when the cvar is registered.
|
||||
cfgVar = new ConfigVar(tablePath, 0, CVar.NONE) { Value = tomlValue };
|
||||
_configVars.Add(tablePath, cfgVar);
|
||||
}
|
||||
|
||||
@@ -131,6 +133,8 @@ namespace Robust.Shared.Configuration
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't write if Archive flag is not set.
|
||||
// Don't write if the cVar is the default value.
|
||||
if (!cVar.ConfigModified &&
|
||||
(cVar.Flags & CVar.ARCHIVE) == 0 || value.Equals(cVar.DefaultValue))
|
||||
{
|
||||
@@ -192,6 +196,7 @@ namespace Robust.Shared.Configuration
|
||||
}
|
||||
|
||||
public void RegisterCVar<T>(string name, T defaultValue, CVar flags = CVar.NONE, Action<T>? onValueChanged = null)
|
||||
where T : notnull
|
||||
{
|
||||
Action<object>? valueChangedDelegate = null;
|
||||
if (onValueChanged != null)
|
||||
@@ -202,7 +207,7 @@ namespace Robust.Shared.Configuration
|
||||
RegisterCVar(name, typeof(T), defaultValue, flags, valueChangedDelegate);
|
||||
}
|
||||
|
||||
private void RegisterCVar(string name, Type type, object? defaultValue, CVar flags, Action<object>? onValueChanged)
|
||||
private void RegisterCVar(string name, Type type, object defaultValue, CVar flags, Action<object>? onValueChanged)
|
||||
{
|
||||
DebugTools.Assert(!type.IsEnum || type.GetEnumUnderlyingType() == typeof(int),
|
||||
$"{name}: Enum cvars must have int as underlying type.");
|
||||
@@ -305,10 +310,15 @@ namespace Robust.Shared.Configuration
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetCVar(string name, object value)
|
||||
public virtual void SetCVar(string name, object value)
|
||||
{
|
||||
SetCVarInternal(name, value);
|
||||
}
|
||||
|
||||
private void SetCVarInternal(string name, object value, bool allowSecure = false)
|
||||
{
|
||||
//TODO: Make flags work, required non-derpy net system.
|
||||
if (_configVars.TryGetValue(name, out var cVar) && cVar.Registered && (cVar.Flags & CVar.SECURE) == 0)
|
||||
if (_configVars.TryGetValue(name, out var cVar) && cVar.Registered && (allowSecure || (cVar.Flags & CVar.SECURE) == 0))
|
||||
{
|
||||
if (!Equals(cVar.OverrideValueParsed ?? cVar.Value, value))
|
||||
{
|
||||
@@ -348,6 +358,18 @@ namespace Robust.Shared.Configuration
|
||||
throw new InvalidConfigurationException($"Trying to get unregistered variable '{name}'");
|
||||
}
|
||||
|
||||
public void SetSecureCVar(string name, object value)
|
||||
{
|
||||
SetCVarInternal(name, value, allowSecure: true);
|
||||
}
|
||||
|
||||
|
||||
public void SetSecureCVar<T>(CVarDef<T> def, T value) where T : notnull
|
||||
{
|
||||
SetSecureCVar(def.Name, value);
|
||||
}
|
||||
|
||||
|
||||
public T GetCVar<T>(CVarDef<T> def) where T : notnull
|
||||
{
|
||||
return GetCVar<T>(def.Name);
|
||||
@@ -377,13 +399,14 @@ namespace Robust.Shared.Configuration
|
||||
else
|
||||
{
|
||||
//or add another unregistered CVar
|
||||
var cVar = new ConfigVar(key, null, CVar.NONE) { OverrideValue = value };
|
||||
//Note: the defaultValue is arbitrarily 0, it will get overwritten when the cvar is registered.
|
||||
var cVar = new ConfigVar(key, 0, CVar.NONE) { OverrideValue = value };
|
||||
_configVars.Add(key, cVar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object ParseOverrideValue(string value, Type? type)
|
||||
private static object ParseOverrideValue(string value, Type? type)
|
||||
{
|
||||
if (type == typeof(int))
|
||||
{
|
||||
@@ -434,7 +457,7 @@ namespace Robust.Shared.Configuration
|
||||
/// <summary>
|
||||
/// Holds the data for a single configuration variable.
|
||||
/// </summary>
|
||||
private class ConfigVar
|
||||
protected class ConfigVar
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a CVar.
|
||||
@@ -444,7 +467,7 @@ namespace Robust.Shared.Configuration
|
||||
/// everything after is the CVar name in the TOML document.</param>
|
||||
/// <param name="defaultValue">The default value of this CVar.</param>
|
||||
/// <param name="flags">Optional flags to modify the behavior of this CVar.</param>
|
||||
public ConfigVar(string name, object? defaultValue, CVar flags)
|
||||
public ConfigVar(string name, object defaultValue, CVar flags)
|
||||
{
|
||||
Name = name;
|
||||
DefaultValue = defaultValue;
|
||||
@@ -459,7 +482,7 @@ namespace Robust.Shared.Configuration
|
||||
/// <summary>
|
||||
/// The default value of this CVar.
|
||||
/// </summary>
|
||||
public object? DefaultValue { get; set; }
|
||||
public object DefaultValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional flags to modify the behavior of this CVar.
|
||||
|
||||
311
Robust.Shared/Configuration/NetConfigurationManager.cs
Normal file
311
Robust.Shared/Configuration/NetConfigurationManager.cs
Normal file
@@ -0,0 +1,311 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// A networked configuration manager that controls the replication of
|
||||
/// console variables between client and server.
|
||||
/// </summary>
|
||||
public interface INetConfigurationManager : IConfigurationManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets up the networking for the config manager.
|
||||
/// </summary>
|
||||
void SetupNetworking();
|
||||
|
||||
/// <summary>
|
||||
/// Get a replicated client CVar for a specific client.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">CVar type.</typeparam>
|
||||
/// <param name="channel">channel of the connected client.</param>
|
||||
/// <param name="name">Name of the CVar.</param>
|
||||
/// <returns>Replicated CVar of the client.</returns>
|
||||
T GetClientCVar<T>(INetChannel channel, string name);
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize the CVars marked with <see cref="CVar.REPLICATED"/> with the client.
|
||||
/// This needs to be called once during the client connection.
|
||||
/// </summary>
|
||||
/// <param name="client">Client's NetChannel to sync replicated CVars with.</param>
|
||||
void SyncConnectingClient(INetChannel client);
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize the CVars marked with <see cref="CVar.REPLICATED"/> with the server.
|
||||
/// This needs to be called once when connecting.
|
||||
/// </summary>
|
||||
void SyncWithServer();
|
||||
|
||||
/// <summary>
|
||||
/// Called every tick to process any incoming network messages.
|
||||
/// </summary>
|
||||
void TickProcessMessages();
|
||||
|
||||
/// <summary>
|
||||
/// Flushes any NwCVar messages in the receive buffer.
|
||||
/// </summary>
|
||||
void FlushMessages();
|
||||
|
||||
public event EventHandler ReceivedInitialNwVars;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="INetConfigurationManager"/>
|
||||
internal class NetConfigurationManager : ConfigurationManager, INetConfigurationManager
|
||||
{
|
||||
[Dependency] private readonly INetManager _netManager = null!;
|
||||
[Dependency] private readonly IGameTiming _timing = null!;
|
||||
|
||||
private readonly Dictionary<INetChannel, Dictionary<string, object>> _replicatedCVars = new();
|
||||
private readonly List<MsgConVars> _netVarsMessages = new();
|
||||
|
||||
public event EventHandler? ReceivedInitialNwVars;
|
||||
private bool _receivedInitialNwVars;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetupNetworking()
|
||||
{
|
||||
if(_isServer)
|
||||
{
|
||||
_netManager.Connected += PeerConnected;
|
||||
_netManager.Disconnect += PeerDisconnected;
|
||||
}
|
||||
|
||||
_netManager.RegisterNetMessage<MsgConVars>(MsgConVars.NAME, HandleNetVarMessage);
|
||||
}
|
||||
|
||||
private void PeerConnected(object? sender, NetChannelArgs e)
|
||||
{
|
||||
_replicatedCVars.Add(e.Channel, new Dictionary<string, object>());
|
||||
}
|
||||
|
||||
private void PeerDisconnected(object? sender, NetDisconnectedArgs e)
|
||||
{
|
||||
_replicatedCVars.Remove(e.Channel);
|
||||
}
|
||||
|
||||
private void HandleNetVarMessage(MsgConVars message)
|
||||
{
|
||||
if(!_receivedInitialNwVars)
|
||||
ReceivedInitialNwVars?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
_receivedInitialNwVars = true;
|
||||
_netVarsMessages.Add(message);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void TickProcessMessages()
|
||||
{
|
||||
if(!_timing.InSimulation || _timing.InPrediction)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < _netVarsMessages.Count; i++)
|
||||
{
|
||||
var msg = _netVarsMessages[i];
|
||||
|
||||
if (msg.Tick > _timing.LastRealTick)
|
||||
continue;
|
||||
|
||||
ApplyNetVarChange(msg.MsgChannel, msg.NetworkedVars);
|
||||
|
||||
if(msg.Tick < _timing.LastRealTick)
|
||||
Logger.WarningS("cfg", $"{msg.MsgChannel}: Received late nwVar message ({msg.Tick} < {_timing.LastRealTick} ).");
|
||||
|
||||
_netVarsMessages.RemoveSwap(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void FlushMessages()
|
||||
{
|
||||
_netVarsMessages.Sort(((a, b) => a.Tick.Value.CompareTo(b.Tick.Value)));
|
||||
|
||||
foreach (var msg in _netVarsMessages)
|
||||
{
|
||||
ApplyNetVarChange(msg.MsgChannel, msg.NetworkedVars);
|
||||
}
|
||||
|
||||
_netVarsMessages.Clear();
|
||||
}
|
||||
|
||||
private void ApplyNetVarChange(INetChannel msgChannel, List<(string name, object value)> networkedVars)
|
||||
{
|
||||
Logger.DebugS("cfg", "Handling replicated cvars...");
|
||||
|
||||
foreach (var (name, value) in networkedVars)
|
||||
{
|
||||
if (_netManager.IsClient) // Server sent us a CVar update.
|
||||
{
|
||||
// Actually set the CVar
|
||||
base.SetCVar(name, value);
|
||||
Logger.DebugS("cfg", $"name={name}, val={value}");
|
||||
}
|
||||
else // Client sent us a CVar update
|
||||
{
|
||||
if (!_configVars.TryGetValue(name, out var cVar))
|
||||
{
|
||||
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an unknown CVar '{name}.'");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cVar.Registered)
|
||||
{
|
||||
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an unregistered CVar '{name}.'");
|
||||
continue;
|
||||
}
|
||||
|
||||
if((cVar.Flags & CVar.REPLICATED) != 0)
|
||||
{
|
||||
var clientCVars = _replicatedCVars[msgChannel];
|
||||
|
||||
if (clientCVars.ContainsKey(name))
|
||||
clientCVars[name] = value;
|
||||
else
|
||||
clientCVars.Add(name, value);
|
||||
|
||||
Logger.DebugS("cfg", $"name={name}, val={value}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.WarningS("cfg", $"{msgChannel} tried to replicate an un-replicated CVar '{name}.'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetClientCVar<T>(INetChannel channel, string name)
|
||||
{
|
||||
if (!_configVars.TryGetValue(name, out var cVar) || !cVar.Registered)
|
||||
throw new InvalidConfigurationException($"Trying to get unregistered variable '{name}'");
|
||||
|
||||
if (_replicatedCVars.TryGetValue(channel, out var clientCVars) && clientCVars.TryGetValue(name, out var value))
|
||||
{
|
||||
return (T)value;
|
||||
}
|
||||
|
||||
return (T)(cVar.DefaultValue!);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetCVar(string name, object value)
|
||||
{
|
||||
if (_configVars.TryGetValue(name, out var cVar) && cVar.Registered)
|
||||
{
|
||||
if (_netManager.IsClient)
|
||||
{
|
||||
if (_netManager.IsConnected)
|
||||
{
|
||||
if ((cVar.Flags & CVar.NOT_CONNECTED) != 0)
|
||||
{
|
||||
Logger.WarningS("cfg", $"'{name}' can only be changed when not connected to a server.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((cVar.Flags & CVar.SERVER) != 0)
|
||||
{
|
||||
Logger.WarningS("cfg", $"Only the server can change '{name}'.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidConfigurationException($"Trying to set unregistered variable '{name}'");
|
||||
}
|
||||
|
||||
// Actually set the CVar
|
||||
base.SetCVar(name, value);
|
||||
|
||||
var cvar = _configVars[name];
|
||||
|
||||
// replicate if needed
|
||||
if (_netManager.IsClient)
|
||||
{
|
||||
if ((cvar.Flags & CVar.REPLICATED) == 0)
|
||||
return;
|
||||
|
||||
var msg = _netManager.CreateNetMessage<MsgConVars>();
|
||||
msg.Tick = _timing.CurTick;
|
||||
msg.NetworkedVars = new List<(string name, object value)>
|
||||
{
|
||||
(name, value)
|
||||
};
|
||||
_netManager.ClientSendMessage(msg);
|
||||
}
|
||||
else // Server
|
||||
{
|
||||
if ((cvar.Flags & CVar.REPLICATED) == 0)
|
||||
return;
|
||||
|
||||
var msg = _netManager.CreateNetMessage<MsgConVars>();
|
||||
msg.Tick = _timing.CurTick;
|
||||
msg.NetworkedVars = new List<(string name, object value)>
|
||||
{
|
||||
(name, value)
|
||||
};
|
||||
_netManager.ServerSendToAll(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SyncConnectingClient(INetChannel client)
|
||||
{
|
||||
DebugTools.Assert(_netManager.IsConnected);
|
||||
DebugTools.Assert(_netManager.IsServer);
|
||||
|
||||
Logger.InfoS("cfg", $"{client}: Sending server info...");
|
||||
|
||||
var msg = _netManager.CreateNetMessage<MsgConVars>();
|
||||
msg.Tick = _timing.CurTick;
|
||||
msg.NetworkedVars = GetReplicatedVars();
|
||||
_netManager.ServerSendMessage(msg, client);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SyncWithServer()
|
||||
{
|
||||
DebugTools.Assert(_netManager.IsConnected);
|
||||
DebugTools.Assert(_netManager.IsClient);
|
||||
|
||||
Logger.InfoS("cfg", "Sending client info...");
|
||||
|
||||
var msg = _netManager.CreateNetMessage<MsgConVars>();
|
||||
msg.Tick = _timing.CurTick;
|
||||
msg.NetworkedVars = GetReplicatedVars();
|
||||
_netManager.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
private List<(string name, object value)> GetReplicatedVars()
|
||||
{
|
||||
var nwVars = new List<(string name, object value)>();
|
||||
|
||||
foreach (var cVar in _configVars.Values)
|
||||
{
|
||||
if (!cVar.Registered)
|
||||
return nwVars;
|
||||
|
||||
if ((cVar.Flags & CVar.REPLICATED) == 0)
|
||||
continue;
|
||||
|
||||
if (_netManager.IsClient && (cVar.Flags & CVar.SERVER) != 0)
|
||||
continue;
|
||||
|
||||
nwVars.Add((cVar.Name, cVar.Value ?? cVar.DefaultValue));
|
||||
|
||||
Logger.DebugS("cfg", $"name={cVar.Name}, val={(cVar.Value ?? cVar.DefaultValue)}");
|
||||
}
|
||||
|
||||
return nwVars;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,6 +260,11 @@ namespace Robust.Shared.ContentPack
|
||||
// See II.14.2 in ECMA-335.
|
||||
return;
|
||||
}
|
||||
case MTypeDefined:
|
||||
{
|
||||
// Valid for this to show up, safe to ignore.
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
@@ -471,6 +476,20 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
break;
|
||||
}
|
||||
case HandleKind.TypeDefinition:
|
||||
{
|
||||
try
|
||||
{
|
||||
parent = GetTypeFromDefinition(reader, (TypeDefinitionHandle) memRef.Parent);
|
||||
}
|
||||
catch (UnsupportedMetadataException u)
|
||||
{
|
||||
errors.Add(new SandboxError(u));
|
||||
return null;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case HandleKind.TypeSpecification:
|
||||
{
|
||||
var typeSpec = reader.GetTypeSpecification((TypeSpecificationHandle) memRef.Parent);
|
||||
@@ -501,7 +520,7 @@ namespace Robust.Shared.ContentPack
|
||||
default:
|
||||
{
|
||||
errors.Add(new SandboxError(
|
||||
$"Unsupported member ref parent type: {memRef.Parent}. Name: {memName}"));
|
||||
$"Unsupported member ref parent type: {memRef.Parent.Kind}. Name: {memName}"));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Robust.Shared.ContentPack
|
||||
var fullPath = mountPath / filePath;
|
||||
Logger.DebugS("res.mod", $"Found module '{fullPath}'");
|
||||
|
||||
var asmFile = _res.ContentFileRead(fullPath);
|
||||
using var asmFile = _res.ContentFileRead(fullPath);
|
||||
var (asmRefs, asmName) = GetAssemblyReferenceData(asmFile);
|
||||
|
||||
if (!files.TryAdd(asmName, (fullPath, asmRefs)))
|
||||
@@ -118,8 +118,8 @@ namespace Robust.Shared.ContentPack
|
||||
}
|
||||
else
|
||||
{
|
||||
var assemblyStream = _res.ContentFileRead(path);
|
||||
var symbolsStream = _res.ContentFileReadOrNull(path.WithExtension("pdb"));
|
||||
using var assemblyStream = _res.ContentFileRead(path);
|
||||
using var symbolsStream = _res.ContentFileReadOrNull(path.WithExtension("pdb"));
|
||||
LoadGameAssembly(assemblyStream, symbolsStream, skipVerify: true);
|
||||
}
|
||||
}
|
||||
@@ -241,16 +241,34 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
if (_res.TryContentFileRead(dllPath, out var gameDll))
|
||||
{
|
||||
Logger.DebugS("srv", $"Loading {assemblyName} DLL");
|
||||
|
||||
// see if debug info is present
|
||||
if (_res.TryContentFileRead(new ResourcePath($@"/Assemblies/{assemblyName}.pdb"),
|
||||
out var gamePdb))
|
||||
using (gameDll)
|
||||
{
|
||||
Logger.DebugS("srv", $"Loading {assemblyName} DLL");
|
||||
|
||||
// see if debug info is present
|
||||
if (_res.TryContentFileRead(new ResourcePath($@"/Assemblies/{assemblyName}.pdb"),
|
||||
out var gamePdb))
|
||||
{
|
||||
using (gamePdb)
|
||||
{
|
||||
try
|
||||
{
|
||||
// load the assembly into the process, and bootstrap the GameServer entry point.
|
||||
LoadGameAssembly(gameDll, gamePdb);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("srv", $"Exception loading DLL {assemblyName}.dll: {e.ToStringBetter()}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// load the assembly into the process, and bootstrap the GameServer entry point.
|
||||
LoadGameAssembly(gameDll, gamePdb);
|
||||
LoadGameAssembly(gameDll);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -259,18 +277,6 @@ namespace Robust.Shared.ContentPack
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// load the assembly into the process, and bootstrap the GameServer entry point.
|
||||
LoadGameAssembly(gameDll);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("srv", $"Exception loading DLL {assemblyName}.dll: {e.ToStringBetter()}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.WarningS("eng", $"Could not load {assemblyName} DLL: {dllPath} does not exist in the VFS.");
|
||||
|
||||
@@ -216,7 +216,13 @@ namespace Robust.Shared.ContentPack
|
||||
/// <inheritdoc />
|
||||
public bool ContentFileExists(ResourcePath path)
|
||||
{
|
||||
return TryContentFileRead(path, out var _);
|
||||
if (TryContentFileRead(path, out var stream))
|
||||
{
|
||||
stream.Dispose();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -297,6 +297,12 @@ Types:
|
||||
IEnumerable: { All: True }
|
||||
IEnumerator: { All: True }
|
||||
IReadOnlyList`1: { All: True }
|
||||
System.ComponentModel:
|
||||
PropertyDescriptor: { }
|
||||
ISite: { All: True }
|
||||
IComponent: { All: True }
|
||||
IContainer: { All: True }
|
||||
ITypeDescriptorContext: { All: True }
|
||||
System.Diagnostics.CodeAnalysis:
|
||||
AllowNullAttribute: { All: True }
|
||||
DisallowNullAttribute: { All: True }
|
||||
@@ -436,6 +442,7 @@ Types:
|
||||
IsExternalInit: { All: True }
|
||||
IsReadOnlyAttribute: { All: True }
|
||||
IteratorStateMachineAttribute: { All: True }
|
||||
PreserveBaseOverridesAttribute: { All: True }
|
||||
RuntimeCompatibilityAttribute: { All: True }
|
||||
RuntimeHelpers: { All: True }
|
||||
TaskAwaiter: { All: True }
|
||||
@@ -651,6 +658,7 @@ Types:
|
||||
CancellationTokenSource: { All: True }
|
||||
Interlocked: { All: True }
|
||||
System:
|
||||
IServiceProvider: { All: True }
|
||||
Action: { All: True }
|
||||
Action`1: { All: True }
|
||||
Action`2: { All: True }
|
||||
@@ -845,6 +853,7 @@ Types:
|
||||
Func`15: { All: True }
|
||||
Func`16: { All: True }
|
||||
Func`17: { All: True }
|
||||
Guid: { All: True }
|
||||
HashCode: { All: True }
|
||||
IAsyncDisposable: { All: True }
|
||||
IAsyncResult: { }
|
||||
@@ -1131,6 +1140,7 @@ Types:
|
||||
# Content should never touch that.
|
||||
Methods:
|
||||
- "bool Equals(object)"
|
||||
- "bool Equals(System.Type)"
|
||||
- "bool get_ContainsGenericParameters()"
|
||||
- "bool get_HasElementType()"
|
||||
- "bool get_IsAbstract()"
|
||||
@@ -1250,6 +1260,7 @@ Types:
|
||||
UInt32: { All: True }
|
||||
UInt64: { All: True }
|
||||
UIntPtr: { All: True }
|
||||
Uri: { All: True }
|
||||
ValueTuple: { All: True }
|
||||
ValueTuple`1: { All: True }
|
||||
ValueTuple`2: { All: True }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user