mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee330d0ae9 | ||
|
|
6c44dd9665 | ||
|
|
c81413b0b4 | ||
|
|
88b3a557da | ||
|
|
572eb01290 | ||
|
|
9dab74c9d5 | ||
|
|
e1cb1e1b9c | ||
|
|
a23da702b1 | ||
|
|
ae9c2423ff | ||
|
|
a6dae8e30a | ||
|
|
96c0a4ae1f | ||
|
|
c26ebcbc78 | ||
|
|
8334050411 | ||
|
|
cc67edfc2c | ||
|
|
943ea9e6c8 | ||
|
|
3aa5cefe03 | ||
|
|
c5b34bab69 | ||
|
|
e4f24ec125 | ||
|
|
250971ade7 | ||
|
|
718adf9740 | ||
|
|
5d63aa8c95 | ||
|
|
17af3612a5 | ||
|
|
d2ecf6b9b1 | ||
|
|
2a1eda0d38 | ||
|
|
f0180abeb0 | ||
|
|
720f1b1d05 | ||
|
|
ae45a96753 | ||
|
|
74257c72ee | ||
|
|
cea088f4b4 | ||
|
|
88678e7d58 | ||
|
|
d222e25d22 | ||
|
|
f0d7fbb6f2 | ||
|
|
adec0e71ec | ||
|
|
86d9067d62 | ||
|
|
b989c9dbee | ||
|
|
3101ec1985 | ||
|
|
9ec2da1777 | ||
|
|
0acd28e8f4 | ||
|
|
c340f50ee5 | ||
|
|
2b589215aa | ||
|
|
490a567ad4 | ||
|
|
32f0c49484 | ||
|
|
61113d2434 | ||
|
|
6ba1baa88c | ||
|
|
07867acb9a | ||
|
|
3e28b083b9 | ||
|
|
68d9e13edf | ||
|
|
a0c0a722c9 | ||
|
|
bf4aead1e8 | ||
|
|
39f0d2e974 | ||
|
|
b20ae98f21 | ||
|
|
7899780543 | ||
|
|
0c9e322b3e | ||
|
|
6005208285 | ||
|
|
2b15831349 | ||
|
|
1b2450d1cb | ||
|
|
5f31036ab2 | ||
|
|
8efffc471d | ||
|
|
eb9e842027 | ||
|
|
f9cd9ac12a | ||
|
|
8fd98c75a9 | ||
|
|
7c008e857d | ||
|
|
4de2e35e66 | ||
|
|
d4467acf93 | ||
|
|
f3babcc39f | ||
|
|
f491bb5571 | ||
|
|
b201f10c76 | ||
|
|
a9208c0d29 | ||
|
|
b8cc01d872 | ||
|
|
2d827890e9 | ||
|
|
f86d6ccd3c | ||
|
|
967b76483a | ||
|
|
ef2c0ad8cf | ||
|
|
9ae1352030 | ||
|
|
d8d9b271cc | ||
|
|
122acc5fd5 | ||
|
|
32dea84196 | ||
|
|
91d58dbca4 | ||
|
|
c54b1572f5 | ||
|
|
1fa979c0f6 | ||
|
|
760599171d | ||
|
|
10d295d535 | ||
|
|
0047c5000f | ||
|
|
810a6d190f | ||
|
|
197227dcf6 | ||
|
|
fa23ec8fc6 | ||
|
|
6506171ea0 |
2
.github/workflows/build-test.yml
vendored
2
.github/workflows/build-test.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>9</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -14,12 +14,6 @@
|
||||
<PackageReference Condition="'$(TargetFramework)' == 'net472'" Include="System.Memory" Version="4.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="OpenToolkit.GraphicsLibraryFramework.dll.config">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<configuration>
|
||||
<!-- I actually have no idea whether this works on FreeBSD but it can't hurt to set it as such. -->
|
||||
<dllmap os="linux,freebsd" dll="glfw3.dll" target="glfw.so.3" />
|
||||
<dllmap os="osx" dll="glfw3.dll" target="glfw.3.dylib" />
|
||||
</configuration>
|
||||
25
README.md
25
README.md
@@ -1,25 +1,16 @@
|
||||

|
||||
|
||||
[](https://ci.appveyor.com/project/Silvertorch5/space-station-14/branch/master)
|
||||
[](https://sonarcloud.io/dashboard?id=ss14)
|
||||
Robust Toolbox is an engine primarily being developed for [Space Station 14](https://github.com/space-wizards/space-station-14), although we're working on making it usable for both [singleplayer](https://github.com/space-wizards/RobustToolboxTemplateSingleplayer) and [multiplayer](https://github.com/space-wizards/RobustToolboxTemplate) projects.
|
||||
|
||||
Robust Toolbox is a client/server backend for [Space Station 14](https://github.com/space-wizards/space-station-14).
|
||||
Use the [content repo](https://github.com/space-wizards/space-station-14) for actual development, even if you're modifying the engine itself.
|
||||
|
||||
### *This repository is the *engine* section of SS14. This is the base engine all SS14 servers will be built on. As such, it does not start on its own: it needs the [content repo](https://github.com/space-wizards/space-station-14). Use said repo for actual development, even if you're modifying the engine itself. Think of Robust Toolbox as BYOND in the context of Spacestation 13.*
|
||||
## Project Links
|
||||
|
||||
## Getting in Touch
|
||||
[Website](https://spacestation14.io/) | [Discord](https://discord.gg/t2jac3p) | [Forum](https://forum.spacestation14.io/) | [Steam](https://store.steampowered.com/app/1255460/Space_Station_14/) | [Standalone Download](https://spacestation14.io/about/nightlies/)
|
||||
|
||||
* Website: [spacestation14.io](https://spacestation14.io/)
|
||||
* Discord: [Invite Link](https://discord.gg/t2jac3p)
|
||||
* IRC: `irc.rizon.net#spacebus`
|
||||
* Code Analysis: [Sonar Cloud](https://sonarcloud.io/dashboard?id=ss14)
|
||||
* Automatic Content Builds: [builds.spacestation14.io](https://builds.spacestation14.io/jenkins/)
|
||||
## Documentation/Wiki
|
||||
|
||||
The IRC is setup to relay back and forth to the Discord server so [IRC nerds](https://xkcd.com/1782/) will not be left out.
|
||||
|
||||
## Documentation
|
||||
|
||||
We have various technical documentation articles on the [HackMD Wiki](https://hackmd.io/@ss14/docs/%2F%40ss14%2Ftechnical-documentation-overview).
|
||||
The [HackMD Wiki](https://hackmd.io/@ss14/docs/wiki) has documentation on SS14s content, engine, game design and more. We also have lots of resources for new contributors to the project.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -27,8 +18,8 @@ We are happy to accept contributions from anybody. Get in Discord or IRC if you
|
||||
|
||||
## Building
|
||||
|
||||
**In practice, you usually don't build this repository directly.**
|
||||
This repository is the **engine** part of SS14. It's the base engine all SS14 servers will be built on. As such, it does not start on its own: it needs the [content repo](https://github.com/space-wizards/space-station-14). Think of Robust Toolbox as BYOND in the context of Spacestation 13.
|
||||
|
||||
## Legal Info
|
||||
|
||||
See `legal.md` for licenses and copyright.
|
||||
See [legal.md](https://github.com/space-wizards/RobustToolbox/blob/master/legal.md) for licenses and copyright.
|
||||
|
||||
1
Resources/Locale/en-US/console.ftl
Normal file
1
Resources/Locale/en-US/console.ftl
Normal file
@@ -0,0 +1 @@
|
||||
console-line-edit-placeholder = Command Here
|
||||
54
Resources/Locale/en-US/input.ftl
Normal file
54
Resources/Locale/en-US/input.ftl
Normal file
@@ -0,0 +1,54 @@
|
||||
input-key-Escape = Escape
|
||||
input-key-Control = Control
|
||||
input-key-Shift = Shift
|
||||
input-key-Alt = Alt
|
||||
input-key-Menu = Menu
|
||||
input-key-F1 = F1
|
||||
input-key-F2 = F2
|
||||
input-key-F3 = F3
|
||||
input-key-F4 = F4
|
||||
input-key-F5 = F5
|
||||
input-key-F6 = F6
|
||||
input-key-F7 = F7
|
||||
input-key-F8 = F8
|
||||
input-key-F9 = F9
|
||||
input-key-F10 = F10
|
||||
input-key-F11 = F11
|
||||
input-key-F12 = F12
|
||||
input-key-F13 = F13
|
||||
input-key-F14 = F14
|
||||
input-key-F15 = F15
|
||||
input-key-Pause = Pause
|
||||
input-key-Left = Left
|
||||
input-key-Up = Up
|
||||
input-key-Down = Down
|
||||
input-key-Right = Right
|
||||
input-key-Space = Space
|
||||
input-key-Return = Return
|
||||
input-key-NumpadEnter = Num Enter
|
||||
input-key-BackSpace = Backspace
|
||||
input-key-Tab = Tab
|
||||
input-key-PageUp = Page Up
|
||||
input-key-PageDown = Page Down
|
||||
input-key-End = End
|
||||
input-key-Home = Home
|
||||
input-key-Insert = Insert
|
||||
input-key-Delete = Delete
|
||||
input-key-MouseLeft = Mouse Left
|
||||
input-key-MouseRight = Mouse Right
|
||||
input-key-MouseMiddle = Mouse Middle
|
||||
input-key-MouseButton4 = Mouse 4
|
||||
input-key-MouseButton5 = Mouse 5
|
||||
input-key-MouseButton6 = Mouse 6
|
||||
input-key-MouseButton7 = Mouse 7
|
||||
input-key-MouseButton8 = Mouse 8
|
||||
input-key-MouseButton9 = Mouse 9
|
||||
|
||||
input-key-LSystem-win = Left Win
|
||||
input-key-RSystem-win = Right Win
|
||||
input-key-LSystem-mac = Left Cmd
|
||||
input-key-RSystem-mac = Right Cmd
|
||||
input-key-LSystem-linux = Left Meta
|
||||
input-key-RSystem-linux = Right Meta
|
||||
|
||||
input-key-unknown = <unknown key>
|
||||
@@ -0,0 +1,115 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Benchmarks.Serialization.Definitions;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Copy
|
||||
{
|
||||
public class SerializationCopyBenchmark : SerializationBenchmark
|
||||
{
|
||||
public SerializationCopyBenchmark()
|
||||
{
|
||||
InitializeSerialization();
|
||||
|
||||
DataDefinitionWithString = new DataDefinitionWithString {StringField = "ABC"};
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(new StringReader(SeedDataDefinition.Prototype));
|
||||
|
||||
var seedMapping = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
|
||||
|
||||
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
|
||||
}
|
||||
|
||||
private const string String = "ABC";
|
||||
|
||||
private const int Integer = 1;
|
||||
|
||||
private DataDefinitionWithString DataDefinitionWithString { get; }
|
||||
|
||||
private SeedDataDefinition Seed { get; }
|
||||
|
||||
[Benchmark]
|
||||
public string? CreateCopyString()
|
||||
{
|
||||
return SerializationManager.CreateCopy(String);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int? CreateCopyInteger()
|
||||
{
|
||||
return SerializationManager.CreateCopy(Integer);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataDefinitionWithString? CreateCopyDataDefinitionWithString()
|
||||
{
|
||||
return SerializationManager.CreateCopy(DataDefinitionWithString);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public SeedDataDefinition? CreateCopySeedDataDefinition()
|
||||
{
|
||||
return SerializationManager.CreateCopy(Seed);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public SeedDataDefinition BaselineCreateCopySeedDataDefinition()
|
||||
{
|
||||
// ReSharper disable once UseObjectOrCollectionInitializer
|
||||
var copy = new SeedDataDefinition();
|
||||
|
||||
copy.ID = Seed.ID;
|
||||
copy.Name = Seed.Name;
|
||||
copy.SeedName = Seed.SeedName;
|
||||
copy.SeedNoun = Seed.SeedNoun;
|
||||
copy.DisplayName = Seed.DisplayName;
|
||||
copy.RoundStart = Seed.RoundStart;
|
||||
copy.Mysterious = Seed.Mysterious;
|
||||
copy.Immutable = Seed.Immutable;
|
||||
|
||||
copy.ProductPrototypes = Seed.ProductPrototypes.ToList();
|
||||
copy.Chemicals = Seed.Chemicals.ToDictionary(p => p.Key, p => p.Value);
|
||||
copy.ConsumeGasses = Seed.ConsumeGasses.ToDictionary(p => p.Key, p => p.Value);
|
||||
copy.ExudeGasses = Seed.ExudeGasses.ToDictionary(p => p.Key, p => p.Value);
|
||||
|
||||
copy.NutrientConsumption = Seed.NutrientConsumption;
|
||||
copy.WaterConsumption = Seed.WaterConsumption;
|
||||
copy.IdealHeat = Seed.IdealHeat;
|
||||
copy.HeatTolerance = Seed.HeatTolerance;
|
||||
copy.IdealLight = Seed.IdealLight;
|
||||
copy.LightTolerance = Seed.LightTolerance;
|
||||
copy.ToxinsTolerance = Seed.ToxinsTolerance;
|
||||
copy.LowPressureTolerance = Seed.LowPressureTolerance;
|
||||
copy.HighPressureTolerance = Seed.HighPressureTolerance;
|
||||
copy.PestTolerance = Seed.PestTolerance;
|
||||
copy.WeedTolerance = Seed.WeedTolerance;
|
||||
|
||||
copy.Endurance = Seed.Endurance;
|
||||
copy.Yield = Seed.Yield;
|
||||
copy.Lifespan = Seed.Lifespan;
|
||||
copy.Maturation = Seed.Maturation;
|
||||
copy.Production = Seed.Production;
|
||||
copy.GrowthStages = Seed.GrowthStages;
|
||||
copy.HarvestRepeat = Seed.HarvestRepeat;
|
||||
copy.Potency = Seed.Potency;
|
||||
copy.Ligneous = Seed.Ligneous;
|
||||
|
||||
copy.PlantRsi = Seed.PlantRsi == null
|
||||
? null!
|
||||
: new ResourcePath(Seed.PlantRsi.ToString(), Seed.PlantRsi.Separator);
|
||||
copy.PlantIconState = Seed.PlantIconState;
|
||||
copy.Bioluminescent = Seed.Bioluminescent;
|
||||
copy.BioluminescentColor = Seed.BioluminescentColor;
|
||||
copy.SplatPrototype = Seed.SplatPrototype;
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization
|
||||
namespace Robust.Benchmarks.Serialization.Definitions
|
||||
{
|
||||
[DataDefinition]
|
||||
public class DataDefinitionWithString
|
||||
{
|
||||
[field: DataField("string")]
|
||||
private string StringField { get; } = default!;
|
||||
public string StringField { get; init; } = default!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Definitions
|
||||
{
|
||||
/// <summary>
|
||||
/// Arbitrarily large data definition for benchmarks.
|
||||
/// Taken from content.
|
||||
/// </summary>
|
||||
[Prototype("seed")]
|
||||
public class SeedDataDefinition : IPrototype
|
||||
{
|
||||
public const string Prototype = @"
|
||||
- type: seed
|
||||
id: tobacco
|
||||
name: tobacco
|
||||
seedName: tobacco
|
||||
displayName: tobacco plant
|
||||
productPrototypes:
|
||||
- LeavesTobacco
|
||||
harvestRepeat: Repeat
|
||||
lifespan: 75
|
||||
maturation: 5
|
||||
production: 5
|
||||
yield: 2
|
||||
potency: 20
|
||||
growthStages: 3
|
||||
idealLight: 9
|
||||
idealHeat: 298
|
||||
chemicals:
|
||||
chem.Nicotine:
|
||||
Min: 1
|
||||
Max: 10
|
||||
PotencyDivisor: 10";
|
||||
|
||||
[DataField("id", required: true)] public string ID { get; set; } = default!;
|
||||
|
||||
#region Tracking
|
||||
[DataField("name")] public string Name { get; set; } = string.Empty;
|
||||
[DataField("seedName")] public string SeedName { get; set; } = string.Empty;
|
||||
[DataField("seedNoun")] public string SeedNoun { get; set; } = "seeds";
|
||||
[DataField("displayName")] public string DisplayName { get; set; } = string.Empty;
|
||||
[DataField("roundStart")] public bool RoundStart { get; set; } = true;
|
||||
[DataField("mysterious")] public bool Mysterious { get; set; }
|
||||
[DataField("immutable")] public bool Immutable { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Output
|
||||
[DataField("productPrototypes")]
|
||||
public List<string> ProductPrototypes { get; set; } = new();
|
||||
[DataField("chemicals")]
|
||||
public Dictionary<string, SeedChemQuantity> Chemicals { get; set; } = new();
|
||||
[DataField("consumeGasses")]
|
||||
public Dictionary<Gas, float> ConsumeGasses { get; set; } = new();
|
||||
[DataField("exudeGasses")]
|
||||
public Dictionary<Gas, float> ExudeGasses { get; set; } = new();
|
||||
#endregion
|
||||
|
||||
#region Tolerances
|
||||
[DataField("nutrientConsumption")] public float NutrientConsumption { get; set; } = 0.25f;
|
||||
[DataField("waterConsumption")] public float WaterConsumption { get; set; } = 3f;
|
||||
[DataField("idealHeat")] public float IdealHeat { get; set; } = 293f;
|
||||
[DataField("heatTolerance")] public float HeatTolerance { get; set; } = 20f;
|
||||
[DataField("idealLight")] public float IdealLight { get; set; } = 7f;
|
||||
[DataField("lightTolerance")] public float LightTolerance { get; set; } = 5f;
|
||||
[DataField("toxinsTolerance")] public float ToxinsTolerance { get; set; } = 4f;
|
||||
[DataField("lowPressureTolerance")] public float LowPressureTolerance { get; set; } = 25f;
|
||||
[DataField("highPressureTolerance")] public float HighPressureTolerance { get; set; } = 200f;
|
||||
[DataField("pestTolerance")] public float PestTolerance { get; set; } = 5f;
|
||||
[DataField("weedTolerance")] public float WeedTolerance { get; set; } = 5f;
|
||||
#endregion
|
||||
|
||||
#region General traits
|
||||
[DataField("endurance")] public float Endurance { get; set; } = 100f;
|
||||
[DataField("yield")] public int Yield { get; set; }
|
||||
[DataField("lifespan")] public float Lifespan { get; set; }
|
||||
[DataField("maturation")] public float Maturation { get; set; }
|
||||
[DataField("production")] public float Production { get; set; }
|
||||
[DataField("growthStages")] public int GrowthStages { get; set; } = 6;
|
||||
[DataField("harvestRepeat")] public HarvestType HarvestRepeat { get; set; } = HarvestType.NoRepeat;
|
||||
[DataField("potency")] public float Potency { get; set; } = 1f;
|
||||
[DataField("ligneous")] public bool Ligneous { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Cosmetics
|
||||
[DataField("plantRsi", required: true)] public ResourcePath PlantRsi { get; set; } = default!;
|
||||
[DataField("plantIconState")] public string PlantIconState { get; set; } = "produce";
|
||||
[DataField("bioluminescent")] public bool Bioluminescent { get; set; }
|
||||
[DataField("bioluminescentColor")] public Color BioluminescentColor { get; set; } = Color.White;
|
||||
[DataField("splatPrototype")] public string? SplatPrototype { get; set; }
|
||||
#endregion
|
||||
}
|
||||
|
||||
public enum HarvestType
|
||||
{
|
||||
NoRepeat,
|
||||
Repeat
|
||||
}
|
||||
|
||||
public enum Gas
|
||||
{
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public struct SeedChemQuantity
|
||||
{
|
||||
[DataField("Min")]
|
||||
public int Min;
|
||||
|
||||
[DataField("Max")]
|
||||
public int Max;
|
||||
|
||||
[DataField("PotencyDivisor")]
|
||||
public int PotencyDivisor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Initialize
|
||||
{
|
||||
public class SerializationInitializeBenchmark : SerializationBenchmark
|
||||
{
|
||||
[IterationCleanup]
|
||||
public void IterationCleanup()
|
||||
{
|
||||
SerializationManager.Shutdown();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ISerializationManager Initialize()
|
||||
{
|
||||
InitializeSerialization();
|
||||
return SerializationManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,22 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Server;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Benchmarks.Serialization.Definitions;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Read
|
||||
{
|
||||
public class SerializationReadBenchmark
|
||||
public class SerializationReadBenchmark : SerializationBenchmark
|
||||
{
|
||||
public SerializationReadBenchmark()
|
||||
{
|
||||
IoCManager.InitThread();
|
||||
ServerIoC.RegisterIoC();
|
||||
IoCManager.BuildGraph();
|
||||
|
||||
var assemblies = new[]
|
||||
{
|
||||
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Shared"),
|
||||
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Server"),
|
||||
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Benchmarks")
|
||||
};
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
IoCManager.Resolve<IConfigurationManagerInternal>().LoadCVarsFromAssembly(assembly);
|
||||
}
|
||||
|
||||
IoCManager.Resolve<IReflectionManager>().LoadAssemblies(assemblies);
|
||||
|
||||
SerializationManager = IoCManager.Resolve<ISerializationManager>();
|
||||
SerializationManager.Initialize();
|
||||
InitializeSerialization();
|
||||
|
||||
StringDataDefNode = new MappingDataNode();
|
||||
StringDataDefNode.AddNode(new ValueDataNode("string"), new ValueDataNode("ABC"));
|
||||
StringDataDefNode.Add(new ValueDataNode("string"), new ValueDataNode("ABC"));
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(new StringReader(SeedDataDefinition.Prototype));
|
||||
@@ -46,8 +24,6 @@ namespace Robust.Benchmarks.Serialization.Read
|
||||
SeedNode = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
|
||||
}
|
||||
|
||||
private ISerializationManager SerializationManager { get; }
|
||||
|
||||
private ValueDataNode StringNode { get; } = new("ABC");
|
||||
|
||||
private ValueDataNode IntNode { get; } = new("1");
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization
|
||||
{
|
||||
[Prototype("seed")]
|
||||
public class SeedDataDefinition : IPrototype
|
||||
{
|
||||
public const string Prototype = @"
|
||||
- type: seed
|
||||
id: tobacco
|
||||
name: tobacco
|
||||
seedName: tobacco
|
||||
displayName: tobacco plant
|
||||
productPrototypes:
|
||||
- LeavesTobacco
|
||||
harvestRepeat: Repeat
|
||||
lifespan: 75
|
||||
maturation: 5
|
||||
production: 5
|
||||
yield: 2
|
||||
potency: 20
|
||||
growthStages: 3
|
||||
idealLight: 9
|
||||
idealHeat: 298
|
||||
chemicals:
|
||||
chem.Nicotine:
|
||||
Min: 1
|
||||
Max: 10
|
||||
PotencyDivisor: 10";
|
||||
|
||||
private const string SeedPrototype = "SeedBase";
|
||||
|
||||
[ViewVariables]
|
||||
[field: DataField("id", required: true)]
|
||||
public string ID { get; private init; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier of this seed. Do NOT set this.
|
||||
/// </summary>
|
||||
public int Uid { get; internal set; } = -1;
|
||||
|
||||
#region Tracking
|
||||
|
||||
[ViewVariables] [DataField("name")] public string Name { get; set; } = string.Empty;
|
||||
[ViewVariables] [DataField("seedName")] public string SeedName { get; set; } = string.Empty;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("seedNoun")]
|
||||
public string SeedNoun { get; set; } = "seeds";
|
||||
[ViewVariables] [DataField("displayName")] public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("roundStart")]
|
||||
public bool RoundStart { get; private set; } = true;
|
||||
[ViewVariables] [DataField("mysterious")] public bool Mysterious { get; set; }
|
||||
[ViewVariables] [DataField("immutable")] public bool Immutable { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Output
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("productPrototypes")]
|
||||
public List<string> ProductPrototypes { get; set; } = new();
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("chemicals")]
|
||||
public Dictionary<string, SeedChemQuantity> Chemicals { get; set; } = new();
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("consumeGasses")]
|
||||
public Dictionary<Gas, float> ConsumeGasses { get; set; } = new();
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("exudeGasses")]
|
||||
public Dictionary<Gas, float> ExudeGasses { get; set; } = new();
|
||||
#endregion
|
||||
|
||||
#region Tolerances
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("nutrientConsumption")]
|
||||
public float NutrientConsumption { get; set; } = 0.25f;
|
||||
|
||||
[ViewVariables] [DataField("waterConsumption")] public float WaterConsumption { get; set; } = 3f;
|
||||
[ViewVariables] [DataField("idealHeat")] public float IdealHeat { get; set; } = 293f;
|
||||
[ViewVariables] [DataField("heatTolerance")] public float HeatTolerance { get; set; } = 20f;
|
||||
[ViewVariables] [DataField("idealLight")] public float IdealLight { get; set; } = 7f;
|
||||
[ViewVariables] [DataField("lightTolerance")] public float LightTolerance { get; set; } = 5f;
|
||||
[ViewVariables] [DataField("toxinsTolerance")] public float ToxinsTolerance { get; set; } = 4f;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("lowPressureTolerance")]
|
||||
public float LowPressureTolerance { get; set; } = 25f;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("highPressureTolerance")]
|
||||
public float HighPressureTolerance { get; set; } = 200f;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("pestTolerance")]
|
||||
public float PestTolerance { get; set; } = 5f;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("weedTolerance")]
|
||||
public float WeedTolerance { get; set; } = 5f;
|
||||
#endregion
|
||||
|
||||
#region General traits
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("endurance")]
|
||||
public float Endurance { get; set; } = 100f;
|
||||
[ViewVariables] [DataField("yield")] public int Yield { get; set; }
|
||||
[ViewVariables] [DataField("lifespan")] public float Lifespan { get; set; }
|
||||
[ViewVariables] [DataField("maturation")] public float Maturation { get; set; }
|
||||
[ViewVariables] [DataField("production")] public float Production { get; set; }
|
||||
[ViewVariables] [DataField("growthStages")] public int GrowthStages { get; set; } = 6;
|
||||
[ViewVariables] [DataField("harvestRepeat")] public HarvestType HarvestRepeat { get; set; } = HarvestType.NoRepeat;
|
||||
|
||||
[ViewVariables] [DataField("potency")] public float Potency { get; set; } = 1f;
|
||||
// No, I'm not removing these.
|
||||
//public PlantSpread Spread { get; set; }
|
||||
//public PlantMutation Mutation { get; set; }
|
||||
//public float AlterTemperature { get; set; }
|
||||
//public PlantCarnivorous Carnivorous { get; set; }
|
||||
//public bool Parasite { get; set; }
|
||||
//public bool Hematophage { get; set; }
|
||||
//public bool Thorny { get; set; }
|
||||
//public bool Stinging { get; set; }
|
||||
[DataField("ligneous")]
|
||||
public bool Ligneous { get; set; }
|
||||
// public bool Teleporting { get; set; }
|
||||
// public PlantJuicy Juicy { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Cosmetics
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("plantRsi", required: true)]
|
||||
public ResourcePath PlantRsi { get; set; } = default!;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("plantIconState")]
|
||||
public string PlantIconState { get; set; } = "produce";
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("bioluminescent")]
|
||||
public bool Bioluminescent { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("bioluminescentColor")]
|
||||
public Color BioluminescentColor { get; set; } = Color.White;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("splatPrototype")]
|
||||
public string? SplatPrototype { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public enum HarvestType
|
||||
{
|
||||
NoRepeat,
|
||||
Repeat
|
||||
}
|
||||
|
||||
public enum Gas {}
|
||||
|
||||
[DataDefinition]
|
||||
public struct SeedChemQuantity
|
||||
{
|
||||
[DataField("Min")]
|
||||
public int Min;
|
||||
[DataField("Max")]
|
||||
public int Max;
|
||||
[DataField("PotencyDivisor")]
|
||||
public int PotencyDivisor;
|
||||
}
|
||||
}
|
||||
43
Robust.Benchmarks/Serialization/SerializationBenchmark.cs
Normal file
43
Robust.Benchmarks/Serialization/SerializationBenchmark.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Robust.Server;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization
|
||||
{
|
||||
public abstract class SerializationBenchmark
|
||||
{
|
||||
public SerializationBenchmark()
|
||||
{
|
||||
IoCManager.InitThread();
|
||||
ServerIoC.RegisterIoC();
|
||||
IoCManager.BuildGraph();
|
||||
|
||||
var assemblies = new[]
|
||||
{
|
||||
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Shared"),
|
||||
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Server"),
|
||||
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Benchmarks")
|
||||
};
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
IoCManager.Resolve<IConfigurationManagerInternal>().LoadCVarsFromAssembly(assembly);
|
||||
}
|
||||
|
||||
IoCManager.Resolve<IReflectionManager>().LoadAssemblies(assemblies);
|
||||
|
||||
SerializationManager = IoCManager.Resolve<ISerializationManager>();
|
||||
}
|
||||
|
||||
protected ISerializationManager SerializationManager { get; }
|
||||
|
||||
public void InitializeSerialization()
|
||||
{
|
||||
SerializationManager.Initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Benchmarks.Serialization.Definitions;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Benchmarks.Serialization.Write
|
||||
{
|
||||
public class SerializationWriteBenchmark : SerializationBenchmark
|
||||
{
|
||||
public SerializationWriteBenchmark()
|
||||
{
|
||||
InitializeSerialization();
|
||||
|
||||
DataDefinitionWithString = new DataDefinitionWithString {StringField = "ABC"};
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(new StringReader(SeedDataDefinition.Prototype));
|
||||
|
||||
var seedMapping = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
|
||||
|
||||
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
|
||||
}
|
||||
|
||||
private const string String = "ABC";
|
||||
|
||||
private const int Integer = 1;
|
||||
|
||||
private DataDefinitionWithString DataDefinitionWithString { get; }
|
||||
|
||||
private SeedDataDefinition Seed { get; }
|
||||
|
||||
[Benchmark]
|
||||
public DataNode WriteString()
|
||||
{
|
||||
return SerializationManager.WriteValue(String);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataNode WriteInteger()
|
||||
{
|
||||
return SerializationManager.WriteValue(Integer);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataNode WriteDataDefinitionWithString()
|
||||
{
|
||||
return SerializationManager.WriteValue(DataDefinitionWithString);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataNode WriteSeedDataDefinition()
|
||||
{
|
||||
return SerializationManager.WriteValue(Seed);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataNode BaselineWriteSeedDataDefinition()
|
||||
{
|
||||
var mapping = new MappingDataNode();
|
||||
|
||||
mapping.Add("id", Seed.ID);
|
||||
mapping.Add("name", Seed.Name);
|
||||
mapping.Add("seedName", Seed.SeedName);
|
||||
mapping.Add("displayName", Seed.DisplayName);
|
||||
mapping.Add("productPrototypes", Seed.ProductPrototypes);
|
||||
mapping.Add("harvestRepeat", Seed.HarvestRepeat.ToString());
|
||||
mapping.Add("lifespan", Seed.Lifespan.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("maturation", Seed.Maturation.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("production", Seed.Production.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("yield", Seed.Yield.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("potency", Seed.Potency.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("growthStages", Seed.GrowthStages.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("idealLight", Seed.IdealLight.ToString(CultureInfo.InvariantCulture));
|
||||
mapping.Add("idealHeat", Seed.IdealHeat.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
var chemicals = new MappingDataNode();
|
||||
foreach (var (name, quantity) in Seed.Chemicals)
|
||||
{
|
||||
chemicals.Add(name, new MappingDataNode
|
||||
{
|
||||
["Min"] = new ValueDataNode(quantity.Min.ToString(CultureInfo.InvariantCulture)),
|
||||
["Max"] = new ValueDataNode(quantity.Max.ToString(CultureInfo.InvariantCulture)),
|
||||
["PotencyDivisor"] = new ValueDataNode(quantity.PotencyDivisor.ToString(CultureInfo.InvariantCulture))
|
||||
});
|
||||
}
|
||||
|
||||
mapping.Add("chemicals", chemicals);
|
||||
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,7 +247,6 @@ namespace {nameSpace}
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
Location.None));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,11 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Logger = Robust.Shared.Log.Logger;
|
||||
|
||||
namespace Robust.Client.Audio.Midi
|
||||
@@ -32,24 +34,6 @@ namespace Robust.Client.Audio.Midi
|
||||
/// </returns>
|
||||
IMidiRenderer? GetNewRenderer();
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// Checks whether the file at the given path is a valid midi file or not.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We add this here so content doesn't need to reference NFluidsynth.
|
||||
/// </remarks>
|
||||
bool IsMidiFile(string filename);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the file at the given path is a valid midi file or not.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We add this here so content doesn't need to reference NFluidsynth.
|
||||
/// </remarks>
|
||||
bool IsSoundfontFile(string filename);
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Method called every frame.
|
||||
/// Should be used to update positional audio.
|
||||
@@ -57,6 +41,11 @@ namespace Robust.Client.Audio.Midi
|
||||
/// <param name="frameTime"></param>
|
||||
void FrameUpdate(float frameTime);
|
||||
|
||||
/// <summary>
|
||||
/// Volume, in db.
|
||||
/// </summary>
|
||||
float Volume { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, MIDI support is available.
|
||||
/// </summary>
|
||||
@@ -75,6 +64,7 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public bool IsAvailable
|
||||
{
|
||||
get
|
||||
@@ -85,12 +75,29 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<MidiRenderer> _renderers = new();
|
||||
[ViewVariables]
|
||||
private readonly List<IMidiRenderer> _renderers = new();
|
||||
|
||||
private bool _alive = true;
|
||||
private Settings? _settings;
|
||||
private Thread? _midiThread;
|
||||
private ISawmill _midiSawmill = default!;
|
||||
private float _volume = 0f;
|
||||
private bool _volumeDirty = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Volume
|
||||
{
|
||||
get => _volume;
|
||||
set
|
||||
{
|
||||
if (MathHelper.CloseTo(_volume, value))
|
||||
return;
|
||||
|
||||
_volume = value;
|
||||
_volumeDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly string[] LinuxSoundfonts =
|
||||
{
|
||||
@@ -117,6 +124,7 @@ namespace Robust.Client.Audio.Midi
|
||||
private NFluidsynth.Logger.LoggerDelegate _loggerDelegate = default!;
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
|
||||
private void InitializeFluidsynth()
|
||||
@@ -175,18 +183,6 @@ namespace Robust.Client.Audio.Midi
|
||||
_sawmill.Log(rLevel, message);
|
||||
}
|
||||
|
||||
/*
|
||||
public bool IsMidiFile(string filename)
|
||||
{
|
||||
return SoundFont.IsMidiFile(filename);
|
||||
}
|
||||
|
||||
public bool IsSoundfontFile(string filename)
|
||||
{
|
||||
return SoundFont.IsSoundFont(filename);
|
||||
}
|
||||
*/
|
||||
|
||||
public IMidiRenderer? GetNewRenderer()
|
||||
{
|
||||
if (!FluidsynthInitialized)
|
||||
@@ -250,7 +246,9 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
_renderers.Add(renderer);
|
||||
}
|
||||
|
||||
return renderer;
|
||||
}
|
||||
@@ -268,74 +266,79 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
|
||||
// Update positions of streams every frame.
|
||||
lock (_renderers)
|
||||
foreach (var renderer in _renderers)
|
||||
foreach (var renderer in _renderers)
|
||||
{
|
||||
if (renderer.Disposed)
|
||||
continue;
|
||||
|
||||
if(_volumeDirty)
|
||||
renderer.Source.SetVolume(Volume);
|
||||
|
||||
if (!renderer.Mono)
|
||||
{
|
||||
if (renderer.Disposed)
|
||||
continue;
|
||||
|
||||
if (!renderer.Mono)
|
||||
{
|
||||
renderer.Source.SetGlobal();
|
||||
continue;
|
||||
}
|
||||
|
||||
MapCoordinates? mapPos = null;
|
||||
if (renderer.TrackingCoordinates != null)
|
||||
{
|
||||
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
|
||||
}
|
||||
else if (renderer.TrackingEntity != null)
|
||||
{
|
||||
mapPos = renderer.TrackingEntity.Transform.MapPosition;
|
||||
}
|
||||
|
||||
if (mapPos != null)
|
||||
{
|
||||
var pos = mapPos.Value;
|
||||
if (pos.MapId != _eyeManager.CurrentMap)
|
||||
{
|
||||
renderer.Source.SetVolume(-10000000);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
|
||||
var occlusion = 0f;
|
||||
if (sourceRelative.Length > 0)
|
||||
{
|
||||
occlusion = _broadPhaseSystem.IntersectRayPenetration(
|
||||
pos.MapId,
|
||||
new CollisionRay(
|
||||
pos.Position,
|
||||
sourceRelative.Normalized,
|
||||
OcclusionCollisionMask),
|
||||
sourceRelative.Length,
|
||||
renderer.TrackingEntity);
|
||||
}
|
||||
renderer.Source.SetOcclusion(occlusion);
|
||||
}
|
||||
|
||||
if (renderer.Source.SetPosition(pos.Position))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (renderer.TrackingEntity != null)
|
||||
{
|
||||
renderer.Source.SetVelocity(renderer.TrackingEntity.GlobalLinearVelocity());
|
||||
}
|
||||
|
||||
if (float.IsNaN(pos.Position.X) || float.IsNaN(pos.Position.Y))
|
||||
{
|
||||
// just duck out instead of move to NaN
|
||||
renderer.Source.SetOcclusion(float.MaxValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
_midiSawmill?.Warning("Interrupting positional audio, can't set position.");
|
||||
renderer.Source.StopPlaying();
|
||||
}
|
||||
renderer.Source.SetGlobal();
|
||||
continue;
|
||||
}
|
||||
|
||||
MapCoordinates? mapPos = null;
|
||||
if (renderer.TrackingCoordinates != null)
|
||||
{
|
||||
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
|
||||
}
|
||||
else if (renderer.TrackingEntity != null)
|
||||
{
|
||||
mapPos = renderer.TrackingEntity.Transform.MapPosition;
|
||||
}
|
||||
|
||||
if (mapPos != null)
|
||||
{
|
||||
var pos = mapPos.Value;
|
||||
if (pos.MapId != _eyeManager.CurrentMap)
|
||||
{
|
||||
renderer.Source.SetVolume(-10000000);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
|
||||
var occlusion = 0f;
|
||||
if (sourceRelative.Length > 0)
|
||||
{
|
||||
occlusion = _broadPhaseSystem.IntersectRayPenetration(
|
||||
pos.MapId,
|
||||
new CollisionRay(
|
||||
pos.Position,
|
||||
sourceRelative.Normalized,
|
||||
OcclusionCollisionMask),
|
||||
sourceRelative.Length,
|
||||
renderer.TrackingEntity);
|
||||
}
|
||||
|
||||
renderer.Source.SetOcclusion(occlusion);
|
||||
}
|
||||
|
||||
if (renderer.Source.SetPosition(pos.Position))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (renderer.TrackingEntity != null)
|
||||
{
|
||||
renderer.Source.SetVelocity(renderer.TrackingEntity.GlobalLinearVelocity());
|
||||
}
|
||||
|
||||
if (float.IsNaN(pos.Position.X) || float.IsNaN(pos.Position.Y))
|
||||
{
|
||||
// just duck out instead of move to NaN
|
||||
renderer.Source.SetOcclusion(float.MaxValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
_midiSawmill?.Warning("Interrupting positional audio, can't set position.");
|
||||
renderer.Source.StopPlaying();
|
||||
}
|
||||
}
|
||||
|
||||
_volumeDirty = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -346,6 +349,7 @@ namespace Robust.Client.Audio.Midi
|
||||
while (_alive)
|
||||
{
|
||||
lock (_renderers)
|
||||
{
|
||||
for (var i = 0; i < _renderers.Count; i++)
|
||||
{
|
||||
var renderer = _renderers[i];
|
||||
@@ -353,10 +357,11 @@ namespace Robust.Client.Audio.Midi
|
||||
renderer.Render();
|
||||
else
|
||||
{
|
||||
((IMidiRenderer)renderer).InternalDispose();
|
||||
renderer.InternalDispose();
|
||||
_renderers.Remove(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
@@ -367,9 +372,13 @@ namespace Robust.Client.Audio.Midi
|
||||
_alive = false;
|
||||
_midiThread?.Join();
|
||||
_settings?.Dispose();
|
||||
foreach (var renderer in _renderers)
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
renderer?.Dispose();
|
||||
foreach (var renderer in _renderers)
|
||||
{
|
||||
renderer?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if (FluidsynthInitialized && !_failedInitialize)
|
||||
@@ -424,6 +433,7 @@ namespace Robust.Client.Audio.Midi
|
||||
var span = new Span<byte>(buf.ToPointer(), length);
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
|
||||
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
|
||||
try
|
||||
{
|
||||
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
|
||||
@@ -447,6 +457,7 @@ namespace Robust.Client.Audio.Midi
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -468,10 +479,12 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
public override int Close(IntPtr sfHandle)
|
||||
{
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
if (!_openStreams.Remove((int) sfHandle, out var stream))
|
||||
return -1;
|
||||
|
||||
stream.Dispose();
|
||||
_openStreams.Remove((int) sfHandle);
|
||||
return 0;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using MidiEvent = NFluidsynth.MidiEvent;
|
||||
|
||||
namespace Robust.Client.Audio.Midi
|
||||
@@ -21,6 +22,17 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
public interface IMidiRenderer : IDisposable
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The buffered audio source of this renderer.
|
||||
/// </summary>
|
||||
internal IClydeBufferedAudioSource Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this renderer has been disposed or not.
|
||||
/// </summary>
|
||||
bool Disposed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This controls whether the midi file being played will loop or not.
|
||||
/// </summary>
|
||||
@@ -110,6 +122,11 @@ namespace Robust.Client.Audio.Midi
|
||||
/// </summary>
|
||||
void StopAllNotes();
|
||||
|
||||
/// <summary>
|
||||
/// Render and play MIDI to the audio source.
|
||||
/// </summary>
|
||||
internal void Render();
|
||||
|
||||
/// <summary>
|
||||
/// Loads a new soundfont into the renderer.
|
||||
/// </summary>
|
||||
@@ -159,7 +176,7 @@ namespace Robust.Client.Audio.Midi
|
||||
internal void InternalDispose();
|
||||
}
|
||||
|
||||
public class MidiRenderer : IMidiRenderer
|
||||
internal class MidiRenderer : IMidiRenderer
|
||||
{
|
||||
[Dependency] private readonly IClydeAudio _clydeAudio = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
@@ -188,8 +205,12 @@ namespace Robust.Client.Audio.Midi
|
||||
private readonly object _playerStateLock = new();
|
||||
private SequencerClientId _synthRegister;
|
||||
public IClydeBufferedAudioSource Source { get; set; }
|
||||
IClydeBufferedAudioSource IMidiRenderer.Source => Source;
|
||||
|
||||
[ViewVariables]
|
||||
public bool Disposed { get; private set; } = false;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public byte MidiProgram
|
||||
{
|
||||
get => _midiProgram;
|
||||
@@ -203,6 +224,7 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public byte MidiBank
|
||||
{
|
||||
get => _midiBank;
|
||||
@@ -216,6 +238,7 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public uint MidiSoundfont
|
||||
{
|
||||
get => _midiSoundfont;
|
||||
@@ -229,10 +252,16 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool DisablePercussionChannel { get; set; } = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool DisableProgramChangeEvent { get; set; } = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int PlayerTotalTick => _player?.GetTotalTicks ?? 0;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int PlayerTick
|
||||
{
|
||||
get => _player?.CurrentTick ?? 0;
|
||||
@@ -243,12 +272,19 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public uint SequencerTick => _sequencer?.Tick ?? 0;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public double SequencerTimeScale => _sequencer?.TimeScale ?? 0;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Mono { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public MidiRendererStatus Status { get; private set; } = MidiRendererStatus.None;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool LoopMidi
|
||||
{
|
||||
get => _loopMidi;
|
||||
@@ -260,10 +296,11 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public IEntity? TrackingEntity { get; set; } = null;
|
||||
public EntityCoordinates? TrackingCoordinates { get; set; } = null;
|
||||
|
||||
internal bool Free { get; set; } = false;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityCoordinates? TrackingCoordinates { get; set; } = null;
|
||||
|
||||
internal MidiRenderer(Settings settings, SoundFontLoader soundFontLoader, bool mono = true)
|
||||
{
|
||||
@@ -294,7 +331,11 @@ namespace Robust.Client.Audio.Midi
|
||||
Status = MidiRendererStatus.Input;
|
||||
StopAllNotes();
|
||||
|
||||
_driver = new MidiDriver(_settings, MidiDriverEventHandler);
|
||||
lock (_playerStateLock)
|
||||
{
|
||||
_driver = new MidiDriver(_settings, MidiDriverEventHandler);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -332,8 +373,13 @@ namespace Robust.Client.Audio.Midi
|
||||
{
|
||||
if (Status != MidiRendererStatus.Input) return false;
|
||||
Status = MidiRendererStatus.None;
|
||||
_driver?.Dispose();
|
||||
_driver = null;
|
||||
|
||||
lock (_playerStateLock)
|
||||
{
|
||||
_driver?.Dispose();
|
||||
_driver = null;
|
||||
}
|
||||
|
||||
StopAllNotes();
|
||||
return true;
|
||||
}
|
||||
@@ -357,7 +403,8 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
public void StopAllNotes()
|
||||
{
|
||||
_synth.AllNotesOff(-1);
|
||||
lock(_playerStateLock)
|
||||
_synth.AllNotesOff(-1);
|
||||
}
|
||||
|
||||
public void LoadSoundfont(string filename, bool resetPresets = false)
|
||||
@@ -372,7 +419,12 @@ namespace Robust.Client.Audio.Midi
|
||||
public event Action<Shared.Audio.Midi.MidiEvent>? OnMidiEvent;
|
||||
public event Action? OnMidiPlayerFinished;
|
||||
|
||||
internal void Render(int length = SampleRate / 250)
|
||||
void IMidiRenderer.Render()
|
||||
{
|
||||
Render();
|
||||
}
|
||||
|
||||
private void Render(int length = SampleRate / 250)
|
||||
{
|
||||
if (Disposed) return;
|
||||
|
||||
@@ -452,6 +504,7 @@ namespace Robust.Client.Audio.Midi
|
||||
var timestamp = SequencerTick;
|
||||
var midiEv = (Shared.Audio.Midi.MidiEvent) midiEvent;
|
||||
midiEv.Tick = timestamp;
|
||||
midiEvent.Dispose();
|
||||
SendMidiEvent(midiEv);
|
||||
return 0;
|
||||
}
|
||||
@@ -462,6 +515,7 @@ namespace Robust.Client.Audio.Midi
|
||||
var timestamp = SequencerTick;
|
||||
var midiEv = (Shared.Audio.Midi.MidiEvent) midiEvent;
|
||||
midiEv.Tick = timestamp;
|
||||
midiEvent.Dispose();
|
||||
SendMidiEvent(midiEv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -25,6 +26,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IPlayerManager _playMan = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _entityLookup = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IDiscordRichPresence _discord = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
@@ -165,7 +167,7 @@ namespace Robust.Client
|
||||
/// receiving states when they join the lobby.
|
||||
/// </summary>
|
||||
/// <param name="session">Session of the player.</param>
|
||||
private void OnPlayerJoinedServer(IPlayerSession session)
|
||||
private void OnPlayerJoinedServer(ICommonSession session)
|
||||
{
|
||||
DebugTools.Assert(RunLevel < ClientRunLevel.Connected);
|
||||
OnRunLevelChanged(ClientRunLevel.Connected);
|
||||
@@ -175,20 +177,11 @@ namespace Robust.Client
|
||||
PlayerJoinedServer?.Invoke(this, new PlayerEventArgs(session));
|
||||
}
|
||||
|
||||
private void GameStartedSetup()
|
||||
{
|
||||
_entityManager.Startup();
|
||||
_mapManager.Startup();
|
||||
|
||||
_timing.ResetSimTime();
|
||||
_timing.Paused = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Player is joining the game
|
||||
/// </summary>
|
||||
/// <param name="session">Session of the player.</param>
|
||||
private void OnPlayerJoinedGame(IPlayerSession session)
|
||||
private void OnPlayerJoinedGame(ICommonSession session)
|
||||
{
|
||||
DebugTools.Assert(RunLevel >= ClientRunLevel.Connected);
|
||||
OnRunLevelChanged(ClientRunLevel.InGame);
|
||||
@@ -218,12 +211,22 @@ namespace Robust.Client
|
||||
GameStoppedReset();
|
||||
}
|
||||
|
||||
private void GameStartedSetup()
|
||||
{
|
||||
_entityManager.Startup();
|
||||
_mapManager.Startup();
|
||||
_entityLookup.Startup();
|
||||
|
||||
_timing.ResetSimTime();
|
||||
_timing.Paused = false;
|
||||
}
|
||||
|
||||
private void GameStoppedReset()
|
||||
{
|
||||
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
|
||||
_gameStates.Reset();
|
||||
_playMan.Shutdown();
|
||||
IoCManager.Resolve<IEntityLookup>().Shutdown();
|
||||
_entityLookup.Shutdown();
|
||||
_entityManager.Shutdown();
|
||||
_mapManager.Shutdown();
|
||||
_discord.ClearPresence();
|
||||
@@ -295,12 +298,12 @@ namespace Robust.Client
|
||||
/// <summary>
|
||||
/// The session that triggered the event.
|
||||
/// </summary>
|
||||
private IPlayerSession? Session { get; }
|
||||
private ICommonSession? Session { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the class.
|
||||
/// </summary>
|
||||
public PlayerEventArgs(IPlayerSession? session)
|
||||
public PlayerEventArgs(ICommonSession? session)
|
||||
{
|
||||
Session = session;
|
||||
}
|
||||
|
||||
@@ -45,10 +45,11 @@ namespace Robust.Client
|
||||
IoCManager.Register<IMapManagerInternal, ClientMapManager>();
|
||||
IoCManager.Register<IClientMapManager, ClientMapManager>();
|
||||
IoCManager.Register<IEntityManager, ClientEntityManager>();
|
||||
IoCManager.Register<IEntityLookup, SharedEntityLookup>();
|
||||
IoCManager.Register<IEntityLookup, EntityLookup>();
|
||||
IoCManager.Register<IComponentFactory, ClientComponentFactory>();
|
||||
IoCManager.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
IoCManager.Register<IClydeTileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
IoCManager.Register<GameController, GameController>();
|
||||
IoCManager.Register<IGameController, GameController>();
|
||||
IoCManager.Register<IGameControllerInternal, GameController>();
|
||||
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
|
||||
|
||||
@@ -280,12 +280,12 @@ namespace Robust.Client.Console.Commands
|
||||
internal class SnapGridGetCell : IConsoleCommand
|
||||
{
|
||||
public string Command => "sggcell";
|
||||
public string Help => "sggcell <gridID> <vector2i> [offset]\nThat vector2i param is in the form x<int>,y<int>.";
|
||||
public string Help => "sggcell <gridID> <vector2i>\nThat vector2i param is in the form x<int>,y<int>.";
|
||||
public string Description => "Lists entities on a snap grid cell.";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 2 && args.Length != 3)
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
@@ -293,7 +293,6 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
string gridId = args[0];
|
||||
string indices = args[1];
|
||||
string offset = args.Length == 3 ? args[2] : "Center";
|
||||
|
||||
if (!int.TryParse(args[0], out var id))
|
||||
{
|
||||
@@ -307,29 +306,17 @@ namespace Robust.Client.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
SnapGridOffset selectedOffset;
|
||||
if (Enum.IsDefined(typeof(SnapGridOffset), offset))
|
||||
{
|
||||
selectedOffset = (SnapGridOffset)Enum.Parse(typeof(SnapGridOffset), offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
shell.WriteError("given offset type is not defined");
|
||||
return;
|
||||
}
|
||||
|
||||
var mapMan = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
if (mapMan.GridExists(new GridId(int.Parse(gridId, CultureInfo.InvariantCulture))))
|
||||
{
|
||||
foreach (var entity in
|
||||
mapMan.GetGrid(new GridId(int.Parse(gridId, CultureInfo.InvariantCulture))).GetSnapGridCell(
|
||||
mapMan.GetGrid(new GridId(int.Parse(gridId, CultureInfo.InvariantCulture))).GetAnchoredEntities(
|
||||
new Vector2i(
|
||||
int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture),
|
||||
int.Parse(indices.Split(',')[1], CultureInfo.InvariantCulture)),
|
||||
selectedOffset))
|
||||
int.Parse(indices.Split(',')[1], CultureInfo.InvariantCulture))))
|
||||
{
|
||||
shell.WriteLine(entity.Owner.Uid.ToString());
|
||||
shell.WriteLine(entity.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -678,10 +665,10 @@ namespace Robust.Client.Console.Commands
|
||||
public string Description => "Gets the system clipboard";
|
||||
public string Help => "getclipboard";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = IoCManager.Resolve<IClipboardManager>();
|
||||
shell.WriteLine(mgr.GetText());
|
||||
shell.WriteLine(await mgr.GetText());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1097,15 +1084,8 @@ namespace Robust.Client.Console.Commands
|
||||
var key = (Keyboard.Key) parsed!;
|
||||
|
||||
var name = clyde.GetKeyName(key);
|
||||
var scanCode = clyde.GetKeyScanCode(key);
|
||||
var nameScanCode = clyde.GetKeyNameScanCode(scanCode);
|
||||
|
||||
shell.WriteLine($"name: '{name}' scan code: '{scanCode}' name via scan code: '{nameScanCode}'");
|
||||
}
|
||||
else if (int.TryParse(args[0], out var scanCode))
|
||||
{
|
||||
var nameScanCode = clyde.GetKeyNameScanCode(scanCode);
|
||||
shell.WriteLine($"name via scan code: '{nameScanCode}'");
|
||||
shell.WriteLine($"name: '{name}' ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,34 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class MonitorInfoCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "monitorinfo";
|
||||
public string Description => "";
|
||||
public string Help => "Usage: monitorinfo <id>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 1)
|
||||
{
|
||||
shell.WriteError("Expected one argument.");
|
||||
return;
|
||||
}
|
||||
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
var monitor = clyde.EnumerateMonitors().Single(c => c.Id == int.Parse(args[0]));
|
||||
|
||||
shell.WriteLine($"{monitor.Id}: {monitor.Name}");
|
||||
shell.WriteLine($"Video modes:");
|
||||
|
||||
foreach (var mode in monitor.VideoModes)
|
||||
{
|
||||
shell.WriteLine($" {mode.Width}x{mode.Height} {mode.RefreshRate} Hz {mode.RedBits}/{mode.GreenBits}/{mode.BlueBits}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class SetMonitorCommand : IConsoleCommand
|
||||
{
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
class QuitCommand : IConsoleCommand
|
||||
class HardQuitCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "quit";
|
||||
public string Command => "hardquit";
|
||||
public string Description => "Kills the game client instantly.";
|
||||
public string Help => "Kills the game client instantly, leaving no traces. No telling the server goodbye";
|
||||
|
||||
@@ -14,4 +15,16 @@ namespace Robust.Client.Console.Commands
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
class QuitCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "quit";
|
||||
public string Description => "Shuts down the game client gracefully.";
|
||||
public string Help => "Properly shuts down the game client, notifying the connected server and such.";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
IoCManager.Resolve<IGameController>().Shutdown("quit command used");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ namespace Robust.Client.Console
|
||||
|
||||
var console = new ScriptConsoleServer(this, session);
|
||||
_activeConsoles.Add(session, console);
|
||||
console.Open();
|
||||
// FIXME: When this is Open(), resizing the window will cause its position to get NaN'd.
|
||||
console.OpenCentered();
|
||||
}
|
||||
|
||||
private void ReceiveScriptResponse(MsgScriptResponse message)
|
||||
|
||||
@@ -155,7 +155,7 @@ namespace Robust.Client.Debugging
|
||||
_hoverBodies.Clear();
|
||||
var mouseScreenPos = _inputManager.MouseScreenPosition;
|
||||
var mouseWorldPos = _eyeManager.ScreenToMap(mouseScreenPos).Position;
|
||||
_hoverStartScreen = mouseScreenPos;
|
||||
_hoverStartScreen = mouseScreenPos.Position;
|
||||
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Robust.LoaderApi;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client
|
||||
{
|
||||
internal partial class GameController
|
||||
{
|
||||
private IGameLoop _mainLoop = default!;
|
||||
private IGameLoop? _mainLoop;
|
||||
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
|
||||
private static bool _hasStarted;
|
||||
|
||||
private Thread? _gameThread;
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Start(args);
|
||||
@@ -42,7 +48,7 @@ namespace Robust.Client
|
||||
|
||||
InitIoC(mode);
|
||||
|
||||
var gc = (GameController) IoCManager.Resolve<IGameController>();
|
||||
var gc = IoCManager.Resolve<GameController>();
|
||||
gc.SetCommandLineArgs(args);
|
||||
gc._loaderArgs = loaderArgs;
|
||||
if(options != null)
|
||||
@@ -52,15 +58,8 @@ namespace Robust.Client
|
||||
// we have to disable the separate load context.
|
||||
// Otherwise the content assemblies will be loaded twice which causes *many* fun bugs.
|
||||
gc.ContentStart = contentStart;
|
||||
if (!gc.Startup())
|
||||
{
|
||||
Logger.Fatal("Failed to start game controller!");
|
||||
return;
|
||||
}
|
||||
gc.MainLoop(mode);
|
||||
|
||||
Logger.Debug("Goodbye");
|
||||
IoCManager.Clear();
|
||||
gc.Run(mode);
|
||||
}
|
||||
|
||||
public void OverrideMainLoop(IGameLoop gameLoop)
|
||||
@@ -68,52 +67,68 @@ namespace Robust.Client
|
||||
_mainLoop = gameLoop;
|
||||
}
|
||||
|
||||
public void MainLoop(DisplayMode mode)
|
||||
public void Run(DisplayMode mode, Func<ILogHandler>? logHandlerFactory = null)
|
||||
{
|
||||
if (_mainLoop == null)
|
||||
if (!StartupSystemSplash(logHandlerFactory))
|
||||
{
|
||||
_mainLoop = new GameLoop(_gameTiming)
|
||||
{
|
||||
SleepMode = mode == DisplayMode.Headless ? SleepMode.Delay : SleepMode.None
|
||||
};
|
||||
Logger.Fatal("Failed to start game controller!");
|
||||
return;
|
||||
}
|
||||
|
||||
_mainLoop.Tick += (sender, args) =>
|
||||
if (_clyde.SeparateWindowThread)
|
||||
{
|
||||
if (_mainLoop.Running)
|
||||
{
|
||||
Tick(args);
|
||||
}
|
||||
};
|
||||
var stackSize = _configurationManager.GetCVar(CVars.SysGameThreadStackSize);
|
||||
var priority = (ThreadPriority) _configurationManager.GetCVar(CVars.SysGameThreadPriority);
|
||||
|
||||
_mainLoop.Render += (sender, args) =>
|
||||
{
|
||||
if (_mainLoop.Running)
|
||||
_gameThread = new Thread(() => GameThreadMain(mode), stackSize)
|
||||
{
|
||||
_gameTiming.CurFrame++;
|
||||
_clyde.Render();
|
||||
}
|
||||
};
|
||||
_mainLoop.Input += (sender, args) =>
|
||||
{
|
||||
if (_mainLoop.Running)
|
||||
{
|
||||
Input(args);
|
||||
}
|
||||
};
|
||||
IsBackground = false,
|
||||
Priority = priority,
|
||||
Name = "Game thread",
|
||||
};
|
||||
|
||||
_mainLoop.Update += (sender, args) =>
|
||||
{
|
||||
if (_mainLoop.Running)
|
||||
{
|
||||
Update(args);
|
||||
}
|
||||
};
|
||||
_gameThread.Start();
|
||||
|
||||
// set GameLoop.Running to false to return from this function.
|
||||
_mainLoop.Run();
|
||||
// Will block until game exit
|
||||
_clyde.EnterWindowLoop();
|
||||
|
||||
if (_gameThread.IsAlive)
|
||||
{
|
||||
Logger.Debug("Window loop exited; waiting for game thread to exit");
|
||||
_gameThread.Join();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ContinueStartupAndLoop(mode);
|
||||
}
|
||||
|
||||
Cleanup();
|
||||
|
||||
Logger.Debug("Goodbye");
|
||||
IoCManager.Clear();
|
||||
}
|
||||
|
||||
private void GameThreadMain(DisplayMode mode)
|
||||
{
|
||||
IoCManager.InitThread(_dependencyCollection);
|
||||
|
||||
ContinueStartupAndLoop(mode);
|
||||
|
||||
// Game thread exited, make sure window thread unblocks to finish shutdown.
|
||||
_clyde.TerminateWindowLoop();
|
||||
}
|
||||
|
||||
private void ContinueStartupAndLoop(DisplayMode mode)
|
||||
{
|
||||
if (!StartupContinue(mode))
|
||||
{
|
||||
Logger.Fatal("Failed to start game controller!");
|
||||
return;
|
||||
}
|
||||
|
||||
DebugTools.AssertNotNull(_mainLoop);
|
||||
_mainLoop!.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Management;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.Audio.Midi;
|
||||
@@ -82,15 +82,18 @@ namespace Robust.Client
|
||||
_commandLineArgs = args;
|
||||
}
|
||||
|
||||
public bool Startup(Func<ILogHandler>? logHandlerFactory = null)
|
||||
private bool StartupContinue(DisplayMode displayMode)
|
||||
{
|
||||
if (!StartupSystemSplash(logHandlerFactory))
|
||||
return false;
|
||||
_clyde.InitializePostWindowing();
|
||||
_clyde.SetWindowTitle(Options.DefaultWindowTitle);
|
||||
|
||||
_taskManager.Initialize();
|
||||
_fontManager.SetFontDpi((uint) _configurationManager.GetCVar(CVars.DisplayFontDpi));
|
||||
|
||||
// Disable load context usage on content start.
|
||||
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
|
||||
_modLoader.SetUseLoadContext(!ContentStart);
|
||||
_modLoader.SetEnableSandboxing(Options.Sandboxing);
|
||||
_modLoader.SetEnableSandboxing(false && Options.Sandboxing);
|
||||
|
||||
if (!_modLoader.TryLoadModulesFrom(new ResourcePath("/Assemblies/"), Options.ContentModulePrefix))
|
||||
{
|
||||
@@ -122,7 +125,6 @@ namespace Robust.Client
|
||||
_prototypeManager.Resync();
|
||||
_mapManager.Initialize();
|
||||
_entityManager.Initialize();
|
||||
IoCManager.Resolve<IEntityLookup>().Initialize();
|
||||
_gameStateManager.Initialize();
|
||||
_placementManager.Initialize();
|
||||
_viewVariablesManager.Initialize();
|
||||
@@ -141,6 +143,47 @@ namespace Robust.Client
|
||||
|
||||
GC.Collect();
|
||||
|
||||
// Setup main loop
|
||||
if (_mainLoop == null)
|
||||
{
|
||||
_mainLoop = new GameLoop(_gameTiming)
|
||||
{
|
||||
SleepMode = displayMode == DisplayMode.Headless ? SleepMode.Delay : SleepMode.None
|
||||
};
|
||||
}
|
||||
|
||||
_mainLoop.Tick += (sender, args) =>
|
||||
{
|
||||
if (_mainLoop.Running)
|
||||
{
|
||||
Tick(args);
|
||||
}
|
||||
};
|
||||
|
||||
_mainLoop.Render += (sender, args) =>
|
||||
{
|
||||
if (_mainLoop.Running)
|
||||
{
|
||||
_gameTiming.CurFrame++;
|
||||
_clyde.Render();
|
||||
}
|
||||
};
|
||||
_mainLoop.Input += (sender, args) =>
|
||||
{
|
||||
if (_mainLoop.Running)
|
||||
{
|
||||
Input(args);
|
||||
}
|
||||
};
|
||||
|
||||
_mainLoop.Update += (sender, args) =>
|
||||
{
|
||||
if (_mainLoop.Running)
|
||||
{
|
||||
Update(args);
|
||||
}
|
||||
};
|
||||
|
||||
_clyde.Ready();
|
||||
|
||||
if (!Options.DisableCommandLineConnect &&
|
||||
@@ -159,8 +202,6 @@ namespace Robust.Client
|
||||
|
||||
SetupLogging(_logManager, logHandlerFactory ?? (() => new ConsoleLogHandler()));
|
||||
|
||||
_taskManager.Initialize();
|
||||
|
||||
// Figure out user data directory.
|
||||
var userDataDir = GetUserDataDir();
|
||||
|
||||
@@ -215,18 +256,16 @@ namespace Robust.Client
|
||||
_clyde.KeyUp += KeyUp;
|
||||
_clyde.KeyDown += KeyDown;
|
||||
_clyde.MouseWheel += MouseWheel;
|
||||
_clyde.CloseWindow += Shutdown;
|
||||
_clyde.CloseWindow += args =>
|
||||
{
|
||||
if (args.Window == _clyde.MainWindow)
|
||||
{
|
||||
Shutdown("Main window closed");
|
||||
}
|
||||
};
|
||||
|
||||
// Bring display up as soon as resources are mounted.
|
||||
if (!_clyde.Initialize())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_clyde.SetWindowTitle(Options.DefaultWindowTitle);
|
||||
|
||||
_fontManager.SetFontDpi((uint) _configurationManager.GetCVar(CVars.DisplayFontDpi));
|
||||
return true;
|
||||
return _clyde.InitializePreWindowing();
|
||||
}
|
||||
|
||||
private Stream? VerifierExtraLoadHandler(string arg)
|
||||
@@ -272,8 +311,10 @@ namespace Robust.Client
|
||||
|
||||
public void Shutdown(string? reason = null)
|
||||
{
|
||||
DebugTools.AssertNotNull(_mainLoop);
|
||||
|
||||
// Already got shut down I assume,
|
||||
if (!_mainLoop.Running)
|
||||
if (!_mainLoop!.Running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -9,58 +9,30 @@ namespace Robust.Client.GameObjects
|
||||
public ClientComponentFactory()
|
||||
{
|
||||
// Required for the engine to work
|
||||
Register<MetaDataComponent>();
|
||||
RegisterReference<MetaDataComponent, IMetaDataComponent>();
|
||||
|
||||
// Required for the engine to work
|
||||
Register<TransformComponent>();
|
||||
RegisterReference<TransformComponent, ITransformComponent>();
|
||||
|
||||
Register<MapComponent>();
|
||||
RegisterReference<MapComponent, IMapComponent>();
|
||||
|
||||
Register<MapGridComponent>();
|
||||
RegisterReference<MapGridComponent, IMapGridComponent>();
|
||||
|
||||
Register<PhysicsComponent>();
|
||||
RegisterReference<PhysicsComponent, IPhysBody>();
|
||||
|
||||
Register<CollisionWakeComponent>();
|
||||
|
||||
Register<ContainerManagerComponent>();
|
||||
RegisterReference<ContainerManagerComponent, IContainerManager>();
|
||||
|
||||
RegisterIgnore("KeyBindingInput");
|
||||
|
||||
Register<InputComponent>();
|
||||
|
||||
Register<SpriteComponent>();
|
||||
RegisterReference<SpriteComponent, SharedSpriteComponent>();
|
||||
RegisterReference<SpriteComponent, ISpriteComponent>();
|
||||
|
||||
Register<ClientOccluderComponent>();
|
||||
RegisterReference<ClientOccluderComponent, OccluderComponent>();
|
||||
|
||||
Register<EyeComponent>();
|
||||
RegisterReference<EyeComponent, SharedEyeComponent>();
|
||||
|
||||
Register<AppearanceComponent>();
|
||||
RegisterReference<AppearanceComponent, SharedAppearanceComponent>();
|
||||
|
||||
Register<AppearanceTestComponent>();
|
||||
Register<SnapGridComponent>();
|
||||
|
||||
Register<ClientUserInterfaceComponent>();
|
||||
RegisterReference<ClientUserInterfaceComponent, SharedUserInterfaceComponent>();
|
||||
|
||||
Register<AnimationPlayerComponent>();
|
||||
|
||||
Register<TimerComponent>();
|
||||
RegisterClass<MetaDataComponent>();
|
||||
RegisterClass<TransformComponent>();
|
||||
RegisterClass<MapComponent>();
|
||||
RegisterClass<MapGridComponent>();
|
||||
RegisterClass<PhysicsComponent>();
|
||||
RegisterClass<CollisionWakeComponent>();
|
||||
RegisterClass<ClientUserInterfaceComponent>();
|
||||
RegisterClass<ContainerManagerComponent>();
|
||||
RegisterClass<InputComponent>();
|
||||
RegisterClass<SpriteComponent>();
|
||||
RegisterClass<ClientOccluderComponent>();
|
||||
RegisterClass<EyeComponent>();
|
||||
RegisterClass<AppearanceComponent>();
|
||||
RegisterClass<AppearanceTestComponent>();
|
||||
RegisterClass<SnapGridComponent>();
|
||||
RegisterClass<AnimationPlayerComponent>();
|
||||
RegisterClass<TimerComponent>();
|
||||
|
||||
#if DEBUG
|
||||
Register<DebugExceptionOnAddComponent>();
|
||||
Register<DebugExceptionInitializeComponent>();
|
||||
Register<DebugExceptionStartupComponent>();
|
||||
RegisterClass<DebugExceptionOnAddComponent>();
|
||||
RegisterClass<DebugExceptionInitializeComponent>();
|
||||
RegisterClass<DebugExceptionStartupComponent>();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, ComponentMessage message)
|
||||
{
|
||||
if (!component.NetID.HasValue)
|
||||
|
||||
@@ -10,6 +10,7 @@ using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ComponentReference(typeof(SharedAppearanceComponent))]
|
||||
public sealed class AppearanceComponent : SharedAppearanceComponent
|
||||
{
|
||||
[ViewVariables]
|
||||
|
||||
@@ -10,6 +10,7 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ComponentReference(typeof(SharedEyeComponent))]
|
||||
public class EyeComponent : SharedEyeComponent
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ComponentReference(typeof(OccluderComponent))]
|
||||
internal sealed class ClientOccluderComponent : OccluderComponent
|
||||
{
|
||||
internal SnapGridComponent? SnapGrid { get; private set; }
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
[ViewVariables] private (GridId, Vector2i) _lastPosition;
|
||||
[ViewVariables] internal OccluderDir Occluding { get; private set; }
|
||||
@@ -29,39 +32,36 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
if (Owner.TryGetComponent(out SnapGridComponent? snap))
|
||||
if (Owner.Transform.Anchored)
|
||||
{
|
||||
SnapGrid = snap;
|
||||
SnapGrid.OnPositionChanged += SnapGridOnPositionChanged;
|
||||
|
||||
SnapGridOnPositionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void SnapGridOnPositionChanged()
|
||||
public void SnapGridOnPositionChanged()
|
||||
{
|
||||
SendDirty();
|
||||
_lastPosition = (Owner.Transform.GridID, SnapGrid!.Position);
|
||||
|
||||
if(!Owner.Transform.Anchored)
|
||||
return;
|
||||
|
||||
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
|
||||
_lastPosition = (Owner.Transform.GridID, grid.TileIndicesFor(Owner.Transform.Coordinates));
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
if (SnapGrid != null)
|
||||
{
|
||||
SnapGrid.OnPositionChanged -= SnapGridOnPositionChanged;
|
||||
}
|
||||
|
||||
SendDirty();
|
||||
}
|
||||
|
||||
private void SendDirty()
|
||||
{
|
||||
if (SnapGrid != null)
|
||||
if (Owner.Transform.Anchored)
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
|
||||
new OccluderDirtyEvent(Owner, _lastPosition, SnapGrid.Offset));
|
||||
new OccluderDirtyEvent(Owner, _lastPosition));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,16 +69,18 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
Occluding = OccluderDir.None;
|
||||
|
||||
if (Deleted || SnapGrid == null)
|
||||
if (Deleted || !Owner.Transform.Anchored)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void CheckDir(Direction dir, OccluderDir oclDir)
|
||||
{
|
||||
foreach (var neighbor in SnapGrid.GetInDir(dir))
|
||||
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
|
||||
var position = Owner.Transform.Coordinates;
|
||||
foreach (var neighbor in grid.GetInDir(position, dir))
|
||||
{
|
||||
if (neighbor.TryGetComponent(out ClientOccluderComponent? comp) && comp.Enabled)
|
||||
if (Owner.EntityManager.ComponentManager.TryGetComponent(neighbor, out ClientOccluderComponent? comp) && comp.Enabled)
|
||||
{
|
||||
Occluding |= oclDir;
|
||||
break;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Animations;
|
||||
@@ -167,7 +168,7 @@ namespace Robust.Client.GameObjects
|
||||
set
|
||||
{
|
||||
_radius = MathF.Max(value, 0.01f); // setting radius to 0 causes exceptions, so just use a value close enough to zero that it's unnoticeable.
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedMessage(this));
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedEvent(this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +180,18 @@ namespace Robust.Client.GameObjects
|
||||
Mask = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// What MapId we are intersecting for RenderingTreeSystem.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
internal MapId IntersectingMapId { get; set; } = MapId.Nullspace;
|
||||
|
||||
/// <summary>
|
||||
/// What grids we're on for RenderingTreeSystem.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
internal List<GridId> IntersectingGrids = new();
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
if (_maskPath != null)
|
||||
@@ -230,7 +243,7 @@ namespace Robust.Client.GameObjects
|
||||
if (map != MapId.Nullspace)
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
|
||||
new RenderTreeRemoveLightMessage(this, map));
|
||||
new RenderTreeRemoveLightEvent(this, map));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,11 +261,11 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public struct PointLightRadiusChangedMessage
|
||||
public class PointLightRadiusChangedEvent : EntityEventArgs
|
||||
{
|
||||
public PointLightComponent PointLightComponent { get; }
|
||||
|
||||
public PointLightRadiusChangedMessage(PointLightComponent pointLightComponent)
|
||||
public PointLightRadiusChangedEvent(PointLightComponent pointLightComponent)
|
||||
{
|
||||
PointLightComponent = pointLightComponent;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ComponentReference(typeof(SharedSpriteComponent))]
|
||||
[ComponentReference(typeof(ISpriteComponent))]
|
||||
public sealed class SpriteComponent : SharedSpriteComponent, ISpriteComponent,
|
||||
IComponentDebug, ISerializationHooks
|
||||
{
|
||||
@@ -123,6 +125,18 @@ namespace Robust.Client.GameObjects
|
||||
[DataField("directional")]
|
||||
private bool _directional = true;
|
||||
|
||||
/// <summary>
|
||||
/// What MapId we are intersecting for RenderingTreeSystem.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
internal MapId IntersectingMapId { get; set; } = MapId.Nullspace;
|
||||
|
||||
/// <summary>
|
||||
/// What grids we're on for RenderingTreeSystem.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
internal List<GridId> IntersectingGrids { get; } = new();
|
||||
|
||||
[DataField("layerDatums")]
|
||||
private List<PrototypeLayerData> LayerDatums
|
||||
{
|
||||
@@ -1353,7 +1367,7 @@ namespace Robust.Client.GameObjects
|
||||
if (map != MapId.Nullspace)
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
|
||||
new RenderTreeRemoveSpriteMessage(this, map));
|
||||
new RenderTreeRemoveSpriteEvent(this, map));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1576,9 +1590,10 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendFormat(
|
||||
"vis/depth/scl/rot/ofs/col/diral/dir: {0}/{1}/{2}/{3}/{4}/{5}/{6}/{7}\n",
|
||||
"vis/depth/scl/rot/ofs/col/norot/override/dir: {0}/{1}/{2}/{3}/{4}/{5}/{6}/{8}/{7}\n",
|
||||
Visible, DrawDepth, Scale, Rotation, Offset,
|
||||
Color, Directional, GetDir(RSI.State.DirectionType.Dir8, Owner.Transform.WorldRotation)
|
||||
Color, NoRotation, GetDir(RSI.State.DirectionType.Dir8, Owner.Transform.WorldRotation),
|
||||
DirectionOverride
|
||||
);
|
||||
|
||||
foreach (var layer in Layers)
|
||||
@@ -2176,6 +2191,10 @@ namespace Robust.Client.GameObjects
|
||||
return null;
|
||||
}
|
||||
|
||||
public void QueueDelete()
|
||||
{
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
}
|
||||
@@ -2190,10 +2209,12 @@ namespace Robust.Client.GameObjects
|
||||
return Enumerable.Empty<T>();
|
||||
}
|
||||
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public void SendMessage(IComponent? owner, ComponentMessage message)
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public void SendNetworkMessage(IComponent owner, ComponentMessage message, INetChannel? channel = null)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ComponentReference(typeof(SharedUserInterfaceComponent))]
|
||||
public class ClientUserInterfaceComponent : SharedUserInterfaceComponent, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
@@ -23,6 +23,9 @@ namespace Robust.Client.GameObjects
|
||||
[DataField("interfaces", readOnly: true)]
|
||||
private List<PrototypeData> _interfaceData = new();
|
||||
|
||||
[ViewVariables]
|
||||
public IEnumerable<BoundUserInterface> Interfaces => _openInterfaces.Values;
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
_interfaces.Clear();
|
||||
@@ -33,48 +36,40 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel,
|
||||
ICommonSession? session = null)
|
||||
internal void MessageReceived(BoundUIWrapMessage msg)
|
||||
{
|
||||
base.HandleNetworkMessage(message, netChannel, session);
|
||||
|
||||
switch (message)
|
||||
switch (msg.Message)
|
||||
{
|
||||
case BoundInterfaceMessageWrapMessage wrapped:
|
||||
// Double nested switches who needs readability anyways.
|
||||
switch (wrapped.Message)
|
||||
case OpenBoundInterfaceMessage _:
|
||||
if (_openInterfaces.ContainsKey(msg.UiKey))
|
||||
{
|
||||
case OpenBoundInterfaceMessage _:
|
||||
if (_openInterfaces.ContainsKey(wrapped.UiKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
OpenInterface(wrapped);
|
||||
break;
|
||||
OpenInterface(msg);
|
||||
break;
|
||||
|
||||
case CloseBoundInterfaceMessage _:
|
||||
Close(wrapped.UiKey, true);
|
||||
break;
|
||||
case CloseBoundInterfaceMessage _:
|
||||
Close(msg.UiKey, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (_openInterfaces.TryGetValue(wrapped.UiKey, out var bi))
|
||||
{
|
||||
bi.InternalReceiveMessage(wrapped.Message);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (_openInterfaces.TryGetValue(msg.UiKey, out var bi))
|
||||
{
|
||||
bi.InternalReceiveMessage(msg.Message);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenInterface(BoundInterfaceMessageWrapMessage wrapped)
|
||||
private void OpenInterface(BoundUIWrapMessage wrapped)
|
||||
{
|
||||
var data = _interfaces[wrapped.UiKey];
|
||||
// TODO: This type should be cached, but I'm too lazy.
|
||||
var type = _reflectionManager.LooseGetType(data.ClientType);
|
||||
var boundInterface = (BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new[]{this, wrapped.UiKey});
|
||||
var boundInterface =
|
||||
(BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new[] {this, wrapped.UiKey});
|
||||
boundInterface.Open();
|
||||
_openInterfaces[wrapped.UiKey] = boundInterface;
|
||||
}
|
||||
@@ -86,7 +81,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
if(!remoteCall)
|
||||
if (!remoteCall)
|
||||
SendMessage(new CloseBoundInterfaceMessage(), uiKey);
|
||||
_openInterfaces.Remove(uiKey);
|
||||
boundUserInterface.Dispose();
|
||||
@@ -94,7 +89,8 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
internal void SendMessage(BoundUserInterfaceMessage message, object uiKey)
|
||||
{
|
||||
SendNetworkMessage(new BoundInterfaceMessageWrapMessage(message, uiKey));
|
||||
EntitySystem.Get<UserInterfaceSystem>()
|
||||
.Send(new BoundUIWrapMessage(Owner.Uid, message, uiKey));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,17 +41,6 @@ namespace Robust.Client.GameObjects
|
||||
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
UnsubscribeNetworkEvent<PlayAudioEntityMessage>();
|
||||
UnsubscribeNetworkEvent<PlayAudioGlobalMessage>();
|
||||
UnsubscribeNetworkEvent<PlayAudioPositionalMessage>();
|
||||
UnsubscribeNetworkEvent<StopAudioMessageClient>();
|
||||
|
||||
UnsubscribeLocalEvent<SoundSystem.QueryAudioSystem>();
|
||||
}
|
||||
|
||||
private void StopAudioMessageHandler(StopAudioMessageClient ev)
|
||||
{
|
||||
var stream = _playingClydeStreams.Find(p => p.NetIdentifier == ev.Identifier);
|
||||
|
||||
@@ -6,6 +6,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -20,10 +21,11 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private readonly Queue<IEntity> _dirtyEntities = new();
|
||||
private readonly Queue<EntityUid> _dirtyEntities = new();
|
||||
|
||||
private uint _updateGeneration;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -32,6 +34,8 @@ namespace Robust.Client.GameObjects
|
||||
UpdatesAfter.Add(typeof(PhysicsSystem));
|
||||
|
||||
SubscribeLocalEvent<OccluderDirtyEvent>(HandleDirtyEvent);
|
||||
|
||||
SubscribeLocalEvent<ClientOccluderComponent, SnapGridPositionChangedEvent>(HandleSnapGridMove);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
@@ -47,8 +51,8 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
while (_dirtyEntities.TryDequeue(out var entity))
|
||||
{
|
||||
if (!entity.Deleted
|
||||
&& entity.TryGetComponent(out ClientOccluderComponent? occluder)
|
||||
if (EntityManager.EntityExists(entity)
|
||||
&& ComponentManager.TryGetComponent(entity, out ClientOccluderComponent? occluder)
|
||||
&& occluder.UpdateGeneration != _updateGeneration)
|
||||
{
|
||||
occluder.Update();
|
||||
@@ -58,6 +62,11 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleSnapGridMove(EntityUid uid, ClientOccluderComponent component, SnapGridPositionChangedEvent args)
|
||||
{
|
||||
component.SnapGridOnPositionChanged();
|
||||
}
|
||||
|
||||
private void HandleDirtyEvent(OccluderDirtyEvent ev)
|
||||
{
|
||||
var sender = ev.Sender;
|
||||
@@ -65,13 +74,14 @@ namespace Robust.Client.GameObjects
|
||||
sender.TryGetComponent(out ClientOccluderComponent? iconSmooth)
|
||||
&& iconSmooth.Running)
|
||||
{
|
||||
var snapGrid = sender.GetComponent<SnapGridComponent>();
|
||||
var grid1 = _mapManager.GetGrid(sender.Transform.GridID);
|
||||
var coords = sender.Transform.Coordinates;
|
||||
|
||||
_dirtyEntities.Enqueue(sender);
|
||||
AddValidEntities(snapGrid.GetInDir(Direction.North));
|
||||
AddValidEntities(snapGrid.GetInDir(Direction.South));
|
||||
AddValidEntities(snapGrid.GetInDir(Direction.East));
|
||||
AddValidEntities(snapGrid.GetInDir(Direction.West));
|
||||
_dirtyEntities.Enqueue(sender.Uid);
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.North));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.South));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.East));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.West));
|
||||
}
|
||||
|
||||
// Entity is no longer valid, update around the last position it was at.
|
||||
@@ -79,28 +89,23 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
var pos = ev.LastPosition.Value.pos;
|
||||
|
||||
AddValidEntities(grid.GetSnapGridCell(pos + new Vector2i(1, 0), ev.Offset));
|
||||
AddValidEntities(grid.GetSnapGridCell(pos + new Vector2i(-1, 0), ev.Offset));
|
||||
AddValidEntities(grid.GetSnapGridCell(pos + new Vector2i(0, 1), ev.Offset));
|
||||
AddValidEntities(grid.GetSnapGridCell(pos + new Vector2i(0, -1), ev.Offset));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(1, 0)));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(-1, 0)));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(0, 1)));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(0, -1)));
|
||||
}
|
||||
}
|
||||
|
||||
private void AddValidEntities(IEnumerable<IEntity> candidates)
|
||||
private void AddValidEntities(IEnumerable<EntityUid> candidates)
|
||||
{
|
||||
foreach (var entity in candidates)
|
||||
{
|
||||
if (entity.HasComponent<ClientOccluderComponent>())
|
||||
if (ComponentManager.HasComponent<ClientOccluderComponent>(entity))
|
||||
{
|
||||
_dirtyEntities.Enqueue(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddValidEntities(IEnumerable<IComponent> candidates)
|
||||
{
|
||||
AddValidEntities(candidates.Select(c => c.Owner));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -108,15 +113,13 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
internal sealed class OccluderDirtyEvent : EntityEventArgs
|
||||
{
|
||||
public OccluderDirtyEvent(IEntity sender, (GridId grid, Vector2i pos)? lastPosition, SnapGridOffset offset)
|
||||
public OccluderDirtyEvent(IEntity sender, (GridId grid, Vector2i pos)? lastPosition)
|
||||
{
|
||||
LastPosition = lastPosition;
|
||||
Offset = offset;
|
||||
Sender = sender;
|
||||
}
|
||||
|
||||
public (GridId grid, Vector2i pos)? LastPosition { get; }
|
||||
public SnapGridOffset Offset { get; }
|
||||
public IEntity Sender { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,9 +67,10 @@ namespace Robust.Client.GameObjects
|
||||
//Create effect from creation message
|
||||
var effect = new Effect(message, resourceCache, _mapManager, _entityManager);
|
||||
effect.Deathtime = gameTiming.CurTime + message.LifeTime;
|
||||
if (effect.AttachedEntityUid != null)
|
||||
if (effect.AttachedEntityUid != null
|
||||
&& _entityManager.TryGetEntity(effect.AttachedEntityUid.Value, out var attachedEntity))
|
||||
{
|
||||
effect.AttachedEntity = _entityManager.GetEntity(effect.AttachedEntityUid.Value);
|
||||
effect.AttachedEntity = attachedEntity;
|
||||
}
|
||||
|
||||
_Effects.Add(effect);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Physics;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -6,6 +8,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -47,14 +50,148 @@ namespace Robust.Client.GameObjects
|
||||
_mapManager.OnGridCreated += MapManagerOnGridCreated;
|
||||
_mapManager.OnGridRemoved += MapManagerOnGridRemoved;
|
||||
|
||||
SubscribeLocalEvent<EntMapIdChangedMessage>(EntMapIdChanged);
|
||||
SubscribeLocalEvent<MoveEvent>(EntMoved);
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(EntParentChanged);
|
||||
SubscribeLocalEvent<PointLightRadiusChangedMessage>(PointLightRadiusChanged);
|
||||
SubscribeLocalEvent<RenderTreeRemoveSpriteMessage>(RemoveSprite);
|
||||
SubscribeLocalEvent<RenderTreeRemoveLightMessage>(RemoveLight);
|
||||
SubscribeLocalEvent<SpriteComponent, EntMapIdChangedMessage>(SpriteMapChanged);
|
||||
SubscribeLocalEvent<SpriteComponent, MoveEvent>(SpriteMoved);
|
||||
SubscribeLocalEvent<SpriteComponent, EntParentChangedMessage>(SpriteParentChanged);
|
||||
SubscribeLocalEvent<SpriteComponent, RenderTreeRemoveSpriteEvent>(RemoveSprite);
|
||||
|
||||
SubscribeLocalEvent<PointLightComponent, EntMapIdChangedMessage>(LightMapChanged);
|
||||
SubscribeLocalEvent<PointLightComponent, MoveEvent>(LightMoved);
|
||||
SubscribeLocalEvent<PointLightComponent, EntParentChangedMessage>(LightParentChanged);
|
||||
SubscribeLocalEvent<PointLightComponent, PointLightRadiusChangedEvent>(PointLightRadiusChanged);
|
||||
SubscribeLocalEvent<PointLightComponent, RenderTreeRemoveLightEvent>(RemoveLight);
|
||||
}
|
||||
|
||||
// For the RemoveX methods
|
||||
// If the Transform is removed BEFORE the Sprite/Light,
|
||||
// then the MapIdChanged code will handle and remove it (because MapId gets set to nullspace).
|
||||
// Otherwise these will still have their past MapId and that's all we need..
|
||||
|
||||
#region SpriteHandlers
|
||||
private void SpriteMapChanged(EntityUid uid, SpriteComponent component, EntMapIdChangedMessage args)
|
||||
{
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
|
||||
private void SpriteMoved(EntityUid uid, SpriteComponent component, MoveEvent args)
|
||||
{
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
|
||||
private void SpriteParentChanged(EntityUid uid, SpriteComponent component, EntParentChangedMessage args)
|
||||
{
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
|
||||
private void RemoveSprite(EntityUid uid, SpriteComponent component, RenderTreeRemoveSpriteEvent args)
|
||||
{
|
||||
ClearSprite(component);
|
||||
}
|
||||
|
||||
private void ClearSprite(SpriteComponent component)
|
||||
{
|
||||
if (_gridTrees.TryGetValue(component.IntersectingMapId, out var gridTrees))
|
||||
{
|
||||
foreach (var gridId in component.IntersectingGrids)
|
||||
{
|
||||
if (!gridTrees.TryGetValue(gridId, out var tree)) continue;
|
||||
tree.SpriteTree.Remove(component);
|
||||
}
|
||||
}
|
||||
|
||||
component.IntersectingGrids.Clear();
|
||||
}
|
||||
|
||||
private void QueueSpriteUpdate(SpriteComponent component)
|
||||
{
|
||||
if (component.TreeUpdateQueued) return;
|
||||
|
||||
component.TreeUpdateQueued = true;
|
||||
_spriteQueue.Add(component);
|
||||
|
||||
foreach (var child in component.Owner.Transform.Children)
|
||||
{
|
||||
QueueSpriteUpdate(child.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueSpriteUpdate(IEntity entity)
|
||||
{
|
||||
if (!entity.TryGetComponent(out SpriteComponent? spriteComponent)) return;
|
||||
QueueSpriteUpdate(spriteComponent);
|
||||
|
||||
foreach (var child in entity.Transform.Children)
|
||||
{
|
||||
QueueSpriteUpdate(child.Owner);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region LightHandlers
|
||||
private void LightMapChanged(EntityUid uid, PointLightComponent component, EntMapIdChangedMessage args)
|
||||
{
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
|
||||
private void LightMoved(EntityUid uid, PointLightComponent component, MoveEvent args)
|
||||
{
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
|
||||
private void LightParentChanged(EntityUid uid, PointLightComponent component, EntParentChangedMessage args)
|
||||
{
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
|
||||
private void PointLightRadiusChanged(EntityUid uid, PointLightComponent component, PointLightRadiusChangedEvent args)
|
||||
{
|
||||
QueueLightUpdate(component);
|
||||
}
|
||||
|
||||
private void RemoveLight(EntityUid uid, PointLightComponent component, RenderTreeRemoveLightEvent args)
|
||||
{
|
||||
ClearLight(component);
|
||||
}
|
||||
|
||||
private void ClearLight(PointLightComponent component)
|
||||
{
|
||||
if (_gridTrees.TryGetValue(component.IntersectingMapId, out var gridTrees))
|
||||
{
|
||||
foreach (var gridId in component.IntersectingGrids)
|
||||
{
|
||||
if (!gridTrees.TryGetValue(gridId, out var tree)) continue;
|
||||
tree.LightTree.Remove(component);
|
||||
}
|
||||
}
|
||||
|
||||
component.IntersectingGrids.Clear();
|
||||
}
|
||||
|
||||
private void QueueLightUpdate(PointLightComponent component)
|
||||
{
|
||||
if (component.TreeUpdateQueued) return;
|
||||
|
||||
component.TreeUpdateQueued = true;
|
||||
_lightQueue.Add(component);
|
||||
|
||||
foreach (var child in component.Owner.Transform.Children)
|
||||
{
|
||||
QueueLightUpdate(child.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueLightUpdate(IEntity entity)
|
||||
{
|
||||
if (!entity.TryGetComponent(out PointLightComponent? lightComponent)) return;
|
||||
QueueLightUpdate(lightComponent);
|
||||
|
||||
foreach (var child in entity.Transform.Children)
|
||||
{
|
||||
QueueLightUpdate(child.Owner);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
@@ -62,149 +199,27 @@ namespace Robust.Client.GameObjects
|
||||
_mapManager.MapDestroyed -= MapManagerOnMapDestroyed;
|
||||
_mapManager.OnGridCreated -= MapManagerOnGridCreated;
|
||||
_mapManager.OnGridRemoved -= MapManagerOnGridRemoved;
|
||||
|
||||
UnsubscribeLocalEvent<EntMapIdChangedMessage>();
|
||||
UnsubscribeLocalEvent<MoveEvent>();
|
||||
UnsubscribeLocalEvent<EntParentChangedMessage>();
|
||||
UnsubscribeLocalEvent<PointLightRadiusChangedMessage>();
|
||||
UnsubscribeLocalEvent<RenderTreeRemoveSpriteMessage>();
|
||||
UnsubscribeLocalEvent<RenderTreeRemoveLightMessage>();
|
||||
}
|
||||
|
||||
// For these next 2 methods (the Remove* ones):
|
||||
// If the Transform is removed BEFORE the Sprite/Light,
|
||||
// then the MapIdChanged code will handle and remove it (because MapId gets set to nullspace).
|
||||
// Otherwise these will still have their past MapId and that's all we need..
|
||||
private void RemoveLight(RenderTreeRemoveLightMessage ev)
|
||||
{
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Map, MapTrees.LightAabbFunc(ev.Light), true))
|
||||
{
|
||||
_gridTrees[ev.Map][gridId].LightTree.Remove(ev.Light);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveSprite(RenderTreeRemoveSpriteMessage ev)
|
||||
{
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Map, MapTrees.SpriteAabbFunc(ev.Sprite), true))
|
||||
{
|
||||
_gridTrees[ev.Map][gridId].SpriteTree.Remove(ev.Sprite);
|
||||
}
|
||||
}
|
||||
|
||||
private void PointLightRadiusChanged(PointLightRadiusChangedMessage ev)
|
||||
{
|
||||
QueueUpdateLight(ev.PointLightComponent);
|
||||
}
|
||||
|
||||
private void EntParentChanged(EntParentChangedMessage ev)
|
||||
{
|
||||
UpdateEntity(ev.Entity);
|
||||
}
|
||||
|
||||
private void EntMoved(MoveEvent ev)
|
||||
{
|
||||
UpdateEntity(ev.Sender);
|
||||
}
|
||||
|
||||
private void UpdateEntity(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out SpriteComponent? spriteComponent))
|
||||
{
|
||||
if (!spriteComponent.TreeUpdateQueued)
|
||||
{
|
||||
spriteComponent.TreeUpdateQueued = true;
|
||||
|
||||
_spriteQueue.Add(spriteComponent);
|
||||
}
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out PointLightComponent? light))
|
||||
{
|
||||
QueueUpdateLight(light);
|
||||
}
|
||||
|
||||
foreach (var child in entity.Transform.ChildEntityUids)
|
||||
{
|
||||
UpdateEntity(EntityManager.GetEntity(child));
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueUpdateLight(PointLightComponent light)
|
||||
{
|
||||
if (!light.TreeUpdateQueued)
|
||||
{
|
||||
light.TreeUpdateQueued = true;
|
||||
|
||||
_lightQueue.Add(light);
|
||||
}
|
||||
}
|
||||
|
||||
private void EntMapIdChanged(EntMapIdChangedMessage ev)
|
||||
{
|
||||
// Nullspace is a valid map ID for stuff to have but we also aren't gonna bother indexing it.
|
||||
// So that's why there's a GetValueOrDefault.
|
||||
var oldMapTrees = _gridTrees.GetValueOrDefault(ev.OldMapId);
|
||||
|
||||
// TODO: MMMM probably a better way to do this.
|
||||
if (ev.Entity.TryGetComponent(out SpriteComponent? sprite))
|
||||
{
|
||||
if (oldMapTrees != null)
|
||||
{
|
||||
foreach (var (_, gridTree) in oldMapTrees)
|
||||
{
|
||||
gridTree.SpriteTree.Remove(sprite);
|
||||
}
|
||||
}
|
||||
|
||||
var bounds = MapTrees.SpriteAabbFunc(sprite);
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Entity.Transform.MapID, bounds, true))
|
||||
{
|
||||
Box2 gridBounds;
|
||||
|
||||
if (gridId == GridId.Invalid)
|
||||
{
|
||||
gridBounds = bounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
gridBounds = bounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
|
||||
}
|
||||
_gridTrees[ev.Entity.Transform.MapID][gridId].SpriteTree.AddOrUpdate(sprite, gridBounds);
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.Entity.TryGetComponent(out PointLightComponent? light))
|
||||
{
|
||||
if (oldMapTrees != null)
|
||||
{
|
||||
foreach (var (_, gridTree) in oldMapTrees)
|
||||
{
|
||||
gridTree.LightTree.Remove(light);
|
||||
}
|
||||
}
|
||||
|
||||
var bounds = MapTrees.LightAabbFunc(light);
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Entity.Transform.MapID, bounds, true))
|
||||
{
|
||||
Box2 gridBounds;
|
||||
|
||||
if (gridId == GridId.Invalid)
|
||||
{
|
||||
gridBounds = bounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
gridBounds = bounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
|
||||
}
|
||||
_gridTrees[ev.Entity.Transform.MapID][gridId].LightTree.AddOrUpdate(light, gridBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MapManagerOnMapDestroyed(object? sender, MapEventArgs e)
|
||||
{
|
||||
foreach (var (_, gridTree) in _gridTrees[e.Map])
|
||||
{
|
||||
foreach (var comp in gridTree.LightTree)
|
||||
{
|
||||
comp.IntersectingGrids.Clear();
|
||||
}
|
||||
|
||||
foreach (var comp in gridTree.SpriteTree)
|
||||
{
|
||||
comp.IntersectingGrids.Clear();
|
||||
}
|
||||
|
||||
// Just in case?
|
||||
gridTree.LightTree.Clear();
|
||||
gridTree.SpriteTree.Clear();
|
||||
}
|
||||
|
||||
_gridTrees.Remove(e.Map);
|
||||
}
|
||||
|
||||
@@ -228,47 +243,109 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void MapManagerOnGridRemoved(MapId mapId, GridId gridId)
|
||||
{
|
||||
var gridTree = _gridTrees[mapId][gridId];
|
||||
|
||||
foreach (var sprite in gridTree.SpriteTree)
|
||||
{
|
||||
sprite.IntersectingGrids.Remove(gridId);
|
||||
}
|
||||
|
||||
foreach (var light in gridTree.LightTree)
|
||||
{
|
||||
light.IntersectingGrids.Remove(gridId);
|
||||
}
|
||||
|
||||
// Clear in case
|
||||
gridTree.LightTree.Clear();
|
||||
gridTree.SpriteTree.Clear();
|
||||
_gridTrees[mapId].Remove(gridId);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
foreach (var queuedUpdateSprite in _spriteQueue)
|
||||
foreach (var sprite in _spriteQueue)
|
||||
{
|
||||
var map = queuedUpdateSprite.Owner.Transform.MapID;
|
||||
if (map == MapId.Nullspace)
|
||||
var mapId = sprite.Owner.Transform.MapID;
|
||||
|
||||
// If we're on a new map then clear the old one.
|
||||
if (sprite.IntersectingMapId != mapId)
|
||||
{
|
||||
continue;
|
||||
ClearSprite(sprite);
|
||||
}
|
||||
|
||||
var mapTree = _gridTrees[map];
|
||||
sprite.IntersectingMapId = mapId;
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map,
|
||||
MapTrees.SpriteAabbFunc(queuedUpdateSprite), true))
|
||||
if (mapId == MapId.Nullspace) continue;
|
||||
|
||||
var mapTree = _gridTrees[mapId];
|
||||
var aabb = MapTrees.SpriteAabbFunc(sprite);
|
||||
var intersectingGrids = _mapManager.FindGridIdsIntersecting(mapId, aabb, true).ToList();
|
||||
|
||||
// Remove from old
|
||||
foreach (var gridId in sprite.IntersectingGrids)
|
||||
{
|
||||
mapTree[gridId].SpriteTree.AddOrUpdate(queuedUpdateSprite);
|
||||
if (intersectingGrids.Contains(gridId)) continue;
|
||||
mapTree[gridId].SpriteTree.Remove(sprite);
|
||||
}
|
||||
|
||||
queuedUpdateSprite.TreeUpdateQueued = false;
|
||||
// Rebuild in the update below
|
||||
sprite.IntersectingGrids.Clear();
|
||||
|
||||
// Update / add to new
|
||||
foreach (var gridId in intersectingGrids)
|
||||
{
|
||||
var translated = aabb.Translated(gridId == GridId.Invalid
|
||||
? Vector2.Zero
|
||||
: -_mapManager.GetGrid(gridId).WorldPosition);
|
||||
|
||||
mapTree[gridId].SpriteTree.AddOrUpdate(sprite, translated);
|
||||
|
||||
sprite.IntersectingGrids.Add(gridId);
|
||||
}
|
||||
|
||||
sprite.TreeUpdateQueued = false;
|
||||
}
|
||||
|
||||
foreach (var queuedUpdateLight in _lightQueue)
|
||||
foreach (var light in _lightQueue)
|
||||
{
|
||||
var map = queuedUpdateLight.Owner.Transform.MapID;
|
||||
if (map == MapId.Nullspace)
|
||||
var mapId = light.Owner.Transform.MapID;
|
||||
|
||||
// If we're on a new map then clear the old one.
|
||||
if (light.IntersectingMapId != mapId)
|
||||
{
|
||||
continue;
|
||||
ClearLight(light);
|
||||
}
|
||||
|
||||
var mapTree = _gridTrees[map];
|
||||
light.IntersectingMapId = mapId;
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(map,
|
||||
MapTrees.LightAabbFunc(queuedUpdateLight), true))
|
||||
if (mapId == MapId.Nullspace) continue;
|
||||
|
||||
var mapTree = _gridTrees[mapId];
|
||||
var aabb = MapTrees.LightAabbFunc(light);
|
||||
var intersectingGrids = _mapManager.FindGridIdsIntersecting(mapId, aabb, true).ToList();
|
||||
|
||||
// Remove from old
|
||||
foreach (var gridId in intersectingGrids)
|
||||
{
|
||||
mapTree[gridId].LightTree.AddOrUpdate(queuedUpdateLight);
|
||||
if (intersectingGrids.Contains(gridId)) continue;
|
||||
mapTree[gridId].LightTree.Remove(light);
|
||||
}
|
||||
|
||||
queuedUpdateLight.TreeUpdateQueued = false;
|
||||
// Rebuild in the update below
|
||||
light.IntersectingGrids.Clear();
|
||||
|
||||
// Update / add to new
|
||||
foreach (var gridId in intersectingGrids)
|
||||
{
|
||||
var translated = aabb.Translated(gridId == GridId.Invalid
|
||||
? Vector2.Zero
|
||||
: -_mapManager.GetGrid(gridId).WorldPosition);
|
||||
|
||||
mapTree[gridId].LightTree.AddOrUpdate(light, translated);
|
||||
light.IntersectingGrids.Add(gridId);
|
||||
}
|
||||
|
||||
light.TreeUpdateQueued = false;
|
||||
}
|
||||
|
||||
_spriteQueue.Clear();
|
||||
@@ -303,9 +380,9 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
internal struct RenderTreeRemoveSpriteMessage
|
||||
internal class RenderTreeRemoveSpriteEvent : EntityEventArgs
|
||||
{
|
||||
public RenderTreeRemoveSpriteMessage(SpriteComponent sprite, MapId map)
|
||||
public RenderTreeRemoveSpriteEvent(SpriteComponent sprite, MapId map)
|
||||
{
|
||||
Sprite = sprite;
|
||||
Map = map;
|
||||
@@ -315,9 +392,9 @@ namespace Robust.Client.GameObjects
|
||||
public MapId Map { get; }
|
||||
}
|
||||
|
||||
internal struct RenderTreeRemoveLightMessage
|
||||
internal class RenderTreeRemoveLightEvent : EntityEventArgs
|
||||
{
|
||||
public RenderTreeRemoveLightMessage(PointLightComponent light, MapId map)
|
||||
public RenderTreeRemoveLightEvent(PointLightComponent light, MapId map)
|
||||
{
|
||||
Light = light;
|
||||
Map = map;
|
||||
|
||||
@@ -16,11 +16,17 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private RenderingTreeSystem _treeSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_treeSystem = Get<RenderingTreeSystem>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
var renderTreeSystem = EntitySystemManager.GetEntitySystem<RenderingTreeSystem>();
|
||||
|
||||
// So we could calculate the correct size of the entities based on the contents of their sprite...
|
||||
// Or we can just assume that no entity is larger than 10x10 and get a stupid easy check.
|
||||
var pvsBounds = _eyeManager.GetWorldViewport().Enlarged(5);
|
||||
@@ -33,18 +39,9 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var gridId in _mapManager.FindGridIdsIntersecting(currentMap, pvsBounds, true))
|
||||
{
|
||||
Box2 gridBounds;
|
||||
var gridBounds = gridId == GridId.Invalid ? pvsBounds : pvsBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
|
||||
|
||||
if (gridId == GridId.Invalid)
|
||||
{
|
||||
gridBounds = pvsBounds;
|
||||
}
|
||||
else
|
||||
{
|
||||
gridBounds = pvsBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition);
|
||||
}
|
||||
|
||||
var mapTree = renderTreeSystem.GetSpriteTreeForMap(currentMap, gridId);
|
||||
var mapTree = _treeSystem.GetSpriteTreeForMap(currentMap, gridId);
|
||||
|
||||
mapTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
|
||||
{
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class UserInterfaceSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<BoundUIWrapMessage>(MessageReceived);
|
||||
SubscribeLocalEvent<ClientUserInterfaceComponent, ComponentShutdown>(OnUserInterfaceShutdown);
|
||||
}
|
||||
|
||||
private void OnUserInterfaceShutdown(EntityUid uid, ClientUserInterfaceComponent component, ComponentShutdown args)
|
||||
{
|
||||
foreach (var bui in component.Interfaces)
|
||||
{
|
||||
bui.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void MessageReceived(BoundUIWrapMessage ev)
|
||||
{
|
||||
var cmp = ComponentManager.GetComponent<ClientUserInterfaceComponent>(ev.Entity);
|
||||
|
||||
cmp.MessageReceived(ev);
|
||||
}
|
||||
|
||||
internal void Send(BoundUIWrapMessage msg)
|
||||
{
|
||||
RaiseNetworkEvent(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public class PlayerAttachedMsg : ComponentMessage
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public class PlayerDetachedMsg : ComponentMessage
|
||||
{
|
||||
|
||||
|
||||
@@ -146,13 +146,12 @@ namespace Robust.Client.GameStates
|
||||
private void DrawWorld(in OverlayDrawArgs args)
|
||||
{
|
||||
bool pvsEnabled = _configurationManager.GetCVar<bool>("net.pvs");
|
||||
|
||||
if(!pvsEnabled)
|
||||
return;
|
||||
|
||||
float pvsSize = _configurationManager.GetCVar<float>("net.maxupdaterange");
|
||||
float pvsRange = _configurationManager.GetCVar<float>("net.maxupdaterange");
|
||||
var pvsCenter = _eyeManager.CurrentEye.Position;
|
||||
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsSize, pvsSize));
|
||||
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsRange * 2, pvsRange * 2));
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
|
||||
@@ -104,13 +104,17 @@ namespace Robust.Client.Graphics
|
||||
|
||||
public ScreenCoordinates MapToScreen(MapCoordinates point)
|
||||
{
|
||||
return new(WorldToScreen(point.Position));
|
||||
return new(WorldToScreen(point.Position), MainViewport.Window?.Id ?? default);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MapCoordinates ScreenToMap(ScreenCoordinates point)
|
||||
{
|
||||
return ScreenToMap(point.Position);
|
||||
var (pos, window) = point;
|
||||
if (window != MainViewport.Window?.Id)
|
||||
return default;
|
||||
|
||||
return MainViewport.ScreenToMap(pos);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX");
|
||||
|
||||
ConfigurationManager.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
|
||||
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
|
||||
}
|
||||
|
||||
private void _audioCreateContext()
|
||||
@@ -81,7 +81,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void _audioOpenDevice()
|
||||
{
|
||||
var preferredDevice = ConfigurationManager.GetCVar(CVars.AudioDevice);
|
||||
var preferredDevice = _cfg.GetCVar(CVars.AudioDevice);
|
||||
|
||||
// Open device.
|
||||
if (!string.IsNullOrEmpty(preferredDevice))
|
||||
@@ -183,6 +183,32 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (handles.filterHandle != 0) EFX.DeleteFilter(handles.filterHandle);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioRaw(short[] samples, int channels, int sampleRate)
|
||||
{
|
||||
var buffer = AL.GenBuffer();
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (short* ptr = samples)
|
||||
{
|
||||
AL.BufferData(
|
||||
buffer,
|
||||
channels == 1 ? ALFormat.Mono16 : ALFormat.Stereo16,
|
||||
(IntPtr) ptr,
|
||||
samples.Length * 2,
|
||||
sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
// ReSharper disable once PossibleLossOfFraction
|
||||
var length = TimeSpan.FromSeconds(samples.Length / channels / (double) sampleRate);
|
||||
return new AudioStream(handle, length, channels);
|
||||
}
|
||||
|
||||
public void SetMasterVolume(float newVolume)
|
||||
{
|
||||
AL.Listener(ALListenerf.Gain, _baseGain * newVolume);
|
||||
@@ -439,6 +465,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public void SetVolumeDirect(float decibels)
|
||||
{
|
||||
_checkDisposed();
|
||||
var priorOcclusion = 1f;
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
}
|
||||
|
||||
_gain = decibels;
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public void SetOcclusion(float blocks)
|
||||
{
|
||||
_checkDisposed();
|
||||
@@ -747,6 +788,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public void SetVolumeDirect(float masterVolumeDecay)
|
||||
{
|
||||
_checkDisposed();
|
||||
var priorOcclusion = 1f;
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle!.Value, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
}
|
||||
|
||||
_gain = masterVolumeDecay;
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_checkAlError();
|
||||
}
|
||||
|
||||
public void SetPitch(float pitch)
|
||||
{
|
||||
_checkDisposed();
|
||||
@@ -770,7 +826,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
if (SourceHandle == null) return;
|
||||
|
||||
if (!disposing || Thread.CurrentThread != _master._mainThread)
|
||||
if (!disposing || Thread.CurrentThread != _master._gameThread)
|
||||
{
|
||||
// We can't run this code inside another thread so tell Clyde to clear it up later.
|
||||
_master.DeleteBufferedSourceOnMainThread(SourceHandle.Value, FilterHandle);
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using OpenToolkit.GraphicsLibraryFramework;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using GlfwImage = OpenToolkit.GraphicsLibraryFramework.Image;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal sealed partial class Clyde
|
||||
{
|
||||
// These are actually Cursor* but we can't do that because no pointer generic arguments.
|
||||
// Need a queue to dispose cursors since the GLFW methods aren't allowed from non-main thread (finalizers).
|
||||
// And they also aren't re-entrant.
|
||||
private readonly ConcurrentQueue<IntPtr> _cursorDisposeQueue = new();
|
||||
|
||||
private readonly Dictionary<StandardCursorShape, CursorImpl> _standardCursors =
|
||||
new();
|
||||
|
||||
// Keep current active cursor around so it doesn't get garbage collected.
|
||||
private CursorImpl? _currentCursor;
|
||||
|
||||
public ICursor GetStandardCursor(StandardCursorShape shape)
|
||||
{
|
||||
return _standardCursors[shape];
|
||||
}
|
||||
|
||||
public unsafe ICursor CreateCursor(Image<Rgba32> image, Vector2i hotSpot)
|
||||
{
|
||||
fixed (Rgba32* pixPtr = image.GetPixelSpan())
|
||||
{
|
||||
var gImg = new GlfwImage(image.Width, image.Height, (byte*) pixPtr);
|
||||
var (hotX, hotY) = hotSpot;
|
||||
var ptr = GLFW.CreateCursor(gImg, hotX, hotY);
|
||||
|
||||
return new CursorImpl(this, ptr, false);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void SetCursor(ICursor? cursor)
|
||||
{
|
||||
if (_currentCursor == cursor)
|
||||
{
|
||||
// Nothing has to be done!
|
||||
return;
|
||||
}
|
||||
|
||||
if (cursor == null)
|
||||
{
|
||||
_currentCursor = null;
|
||||
GLFW.SetCursor(_glfwWindow, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(cursor is CursorImpl impl) || impl.Owner != this)
|
||||
{
|
||||
throw new ArgumentException("Cursor is not created by this clyde instance.");
|
||||
}
|
||||
|
||||
if (impl.Cursor == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(cursor));
|
||||
}
|
||||
|
||||
_currentCursor = impl;
|
||||
GLFW.SetCursor(_glfwWindow, impl.Cursor);
|
||||
}
|
||||
|
||||
private unsafe void FlushCursorDispose()
|
||||
{
|
||||
while (_cursorDisposeQueue.TryDequeue(out var cursor))
|
||||
{
|
||||
var ptr = (Cursor*) cursor;
|
||||
|
||||
if (_currentCursor != null && ptr == _currentCursor.Cursor)
|
||||
{
|
||||
// Currently active cursor getting disposed.
|
||||
_currentCursor = null;
|
||||
}
|
||||
|
||||
GLFW.DestroyCursor(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitCursors()
|
||||
{
|
||||
unsafe void AddStandardCursor(StandardCursorShape standardShape, CursorShape shape)
|
||||
{
|
||||
var ptr = GLFW.CreateStandardCursor(shape);
|
||||
|
||||
var impl = new CursorImpl(this, ptr, true);
|
||||
|
||||
_standardCursors.Add(standardShape, impl);
|
||||
}
|
||||
|
||||
AddStandardCursor(StandardCursorShape.Arrow, CursorShape.Arrow);
|
||||
AddStandardCursor(StandardCursorShape.IBeam, CursorShape.IBeam);
|
||||
AddStandardCursor(StandardCursorShape.Crosshair, CursorShape.Crosshair);
|
||||
AddStandardCursor(StandardCursorShape.Hand, CursorShape.Hand);
|
||||
AddStandardCursor(StandardCursorShape.HResize, CursorShape.HResize);
|
||||
AddStandardCursor(StandardCursorShape.VResize, CursorShape.VResize);
|
||||
}
|
||||
|
||||
private sealed class CursorImpl : ICursor
|
||||
{
|
||||
private readonly bool _standard;
|
||||
public Clyde Owner { get; }
|
||||
public unsafe Cursor* Cursor { get; private set; }
|
||||
|
||||
public unsafe CursorImpl(Clyde clyde, Cursor* pointer, bool standard)
|
||||
{
|
||||
_standard = standard;
|
||||
Owner = clyde;
|
||||
Cursor = pointer;
|
||||
}
|
||||
|
||||
~CursorImpl()
|
||||
{
|
||||
DisposeImpl();
|
||||
}
|
||||
|
||||
private unsafe void DisposeImpl()
|
||||
{
|
||||
Owner._cursorDisposeQueue.Enqueue((IntPtr) Cursor);
|
||||
Cursor = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_standard)
|
||||
{
|
||||
throw new InvalidOperationException("Can't dispose standard cursor shape.");
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
DisposeImpl();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
172
Robust.Client/Graphics/Clyde/Clyde.Events.cs
Normal file
172
Robust.Client/Graphics/Clyde/Clyde.Events.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
// Makes switching easier.
|
||||
#if EXCEPTION_TOLERANCE
|
||||
#define EXCEPTION_TOLERANCE_LOCAL
|
||||
using System;
|
||||
using Robust.Shared.Log;
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal sealed partial class Clyde
|
||||
{
|
||||
// To avoid re-entrancy bollocks we need ANOTHER queue here to actually dispatch our raw input events.
|
||||
// Yes, on top of the two queues inside the GLFW impl.
|
||||
// Because the GLFW-impl queue has to get flushed to avoid deadlocks on window creation
|
||||
// which is ALSO where key events get raised from in a re-entrant fashion. Yay.
|
||||
private readonly Queue<DEventBase> _eventDispatchQueue = new();
|
||||
|
||||
private void DispatchEvents()
|
||||
{
|
||||
while (_eventDispatchQueue.TryDequeue(out var ev))
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE_LOCAL
|
||||
try
|
||||
#endif
|
||||
{
|
||||
DispatchSingleEvent(ev);
|
||||
}
|
||||
#if EXCEPTION_TOLERANCE_LOCAL
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("clyde.win", $"Error dispatching window event {ev.GetType().Name}:\n{e}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void DispatchSingleEvent(DEventBase ev)
|
||||
{
|
||||
switch (ev)
|
||||
{
|
||||
case DEventKeyDown(var args):
|
||||
KeyDown?.Invoke(args);
|
||||
break;
|
||||
case DEventKeyUp(var args):
|
||||
KeyUp?.Invoke(args);
|
||||
break;
|
||||
case DEventMouseMove(var args):
|
||||
MouseMove?.Invoke(args);
|
||||
break;
|
||||
case DEventMouseEnterLeave(var args):
|
||||
MouseEnterLeave?.Invoke(args);
|
||||
break;
|
||||
case DEventScroll(var args):
|
||||
MouseWheel?.Invoke(args);
|
||||
break;
|
||||
case DEventText(var args):
|
||||
TextEntered?.Invoke(args);
|
||||
break;
|
||||
case DEventWindowClosed(var reg, var args):
|
||||
reg.Closed?.Invoke(args);
|
||||
CloseWindow?.Invoke(args);
|
||||
|
||||
if (reg.DisposeOnClose && !reg.IsMainWindow)
|
||||
DoDestroyWindow(reg);
|
||||
break;
|
||||
case DEventWindowContentScaleChanged(var args):
|
||||
OnWindowScaleChanged?.Invoke(args);
|
||||
break;
|
||||
case DEventWindowFocus(var args):
|
||||
OnWindowFocused?.Invoke(args);
|
||||
break;
|
||||
case DEventWindowResized(var args):
|
||||
OnWindowResized?.Invoke(args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void SendKeyUp(KeyEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventKeyUp(ev));
|
||||
}
|
||||
|
||||
private void SendKeyDown(KeyEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventKeyDown(ev));
|
||||
}
|
||||
|
||||
private void SendScroll(MouseWheelEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventScroll(ev));
|
||||
}
|
||||
|
||||
private void SendCloseWindow(WindowReg windowReg, WindowClosedEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventWindowClosed(windowReg, ev));
|
||||
}
|
||||
|
||||
private void SendWindowResized(WindowReg reg, Vector2i oldSize)
|
||||
{
|
||||
if (reg.IsMainWindow)
|
||||
{
|
||||
UpdateMainWindowLoadedRtSize();
|
||||
GL.Viewport(0, 0, reg.FramebufferSize.X, reg.FramebufferSize.Y);
|
||||
CheckGlError();
|
||||
}
|
||||
else
|
||||
{
|
||||
reg.RenderTexture!.Dispose();
|
||||
CreateWindowRenderTexture(reg);
|
||||
}
|
||||
|
||||
var eventArgs = new WindowResizedEventArgs(
|
||||
oldSize,
|
||||
reg.FramebufferSize,
|
||||
reg.Handle);
|
||||
|
||||
_eventDispatchQueue.Enqueue(new DEventWindowResized(eventArgs));
|
||||
}
|
||||
|
||||
private void SendWindowContentScaleChanged(WindowContentScaleEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventWindowContentScaleChanged(ev));
|
||||
}
|
||||
|
||||
private void SendWindowFocus(WindowFocusedEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventWindowFocus(ev));
|
||||
}
|
||||
|
||||
private void SendText(TextEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventText(ev));
|
||||
}
|
||||
|
||||
private void SendMouseMove(MouseMoveEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventMouseMove(ev));
|
||||
}
|
||||
|
||||
private void SendMouseEnterLeave(MouseEnterLeaveEventArgs ev)
|
||||
{
|
||||
_eventDispatchQueue.Enqueue(new DEventMouseEnterLeave(ev));
|
||||
}
|
||||
|
||||
// D stands for Dispatch
|
||||
private abstract record DEventBase;
|
||||
|
||||
private sealed record DEventKeyUp(KeyEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventKeyDown(KeyEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventScroll(MouseWheelEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventWindowClosed(WindowReg Reg, WindowClosedEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventWindowResized(WindowResizedEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventWindowContentScaleChanged(WindowContentScaleEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventWindowFocus(WindowFocusedEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventText(TextEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventMouseMove(MouseMoveEventArgs Args) : DEventBase;
|
||||
private sealed record DEventMouseEnterLeave(MouseEnterLeaveEventArgs Args) : DEventBase;
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
var extensions = GetGLExtensions();
|
||||
|
||||
Logger.DebugS("clyde.ogl", "OpenGL capabilities:");
|
||||
_sawmillOgl.Debug("OpenGL capabilities:");
|
||||
|
||||
if (!_isGLES)
|
||||
{
|
||||
@@ -81,7 +81,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
// We're ES <3.2, KHR_debug is extension and needs KHR suffixes.
|
||||
_isGLKhrDebugESExtension = true;
|
||||
Logger.DebugS("clyde.ogl", " khr_debug is ES extension!");
|
||||
_sawmillOgl.Debug(" khr_debug is ES extension!");
|
||||
}
|
||||
|
||||
CheckGLCap(ref _hasGLVertexArrayObject, "vertex_array_object", (3, 0));
|
||||
@@ -104,7 +104,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// This is 3.2 or extensions
|
||||
_hasGLFloatFramebuffers = !_isGLES;
|
||||
|
||||
Logger.DebugS("clyde.ogl", $" GLES: {_isGLES}");
|
||||
_sawmillOgl.Debug($" GLES: {_isGLES}");
|
||||
|
||||
void CheckGLCap(ref bool cap, string capName, (int major, int minor)? versionMin = null,
|
||||
params string[] exts)
|
||||
@@ -115,15 +115,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var prev = cap;
|
||||
var cVarName = $"display.ogl_block_{capName}";
|
||||
var block = ConfigurationManager.GetCVar<bool>(cVarName);
|
||||
var block = _cfg.GetCVar<bool>(cVarName);
|
||||
|
||||
if (block)
|
||||
{
|
||||
cap = false;
|
||||
Logger.DebugS("clyde.ogl", $" {cVarName} SET, BLOCKING {capName} (was: {prev})");
|
||||
_sawmillOgl.Debug($" {cVarName} SET, BLOCKING {capName} (was: {prev})");
|
||||
}
|
||||
|
||||
Logger.DebugS("clyde.ogl", $" {capName}: {cap}");
|
||||
_sawmillOgl.Debug($" {capName}: {cap}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
foreach (var cvar in cvars)
|
||||
{
|
||||
ConfigurationManager.RegisterCVar($"display.ogl_block_{cvar}", false);
|
||||
_cfg.RegisterCVar($"display.ogl_block_{cvar}", false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,14 +168,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
extensionsText += extension;
|
||||
extensions.Add(extension);
|
||||
}
|
||||
Logger.DebugS("clyde.ogl", "OpenGL Extensions: {0}", extensionsText);
|
||||
_sawmillOgl.Debug("OpenGL Extensions: {0}", extensionsText);
|
||||
return extensions;
|
||||
}
|
||||
else
|
||||
{
|
||||
// GLES uses the (old?) API
|
||||
var extensions = GL.GetString(StringName.Extensions);
|
||||
Logger.DebugS("clyde.ogl", "OpenGL Extensions: {0}", extensions);
|
||||
_sawmillOgl.Debug("OpenGL Extensions: {0}", extensions);
|
||||
return new HashSet<string>(extensions.Split(' '));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,18 +25,28 @@ namespace Robust.Client.Graphics.Clyde
|
||||
=
|
||||
new();
|
||||
|
||||
public void Render()
|
||||
public unsafe void Render()
|
||||
{
|
||||
CheckTransferringScreenshots();
|
||||
|
||||
var allMinimized = true;
|
||||
foreach (var windowReg in _windowing!.AllWindows)
|
||||
{
|
||||
if (!windowReg.IsMinimized)
|
||||
{
|
||||
allMinimized = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var size = ScreenSize;
|
||||
if (size.X == 0 || size.Y == 0 || _isMinimized)
|
||||
if (size.X == 0 || size.Y == 0 || allMinimized)
|
||||
{
|
||||
ClearFramebuffer(Color.Black);
|
||||
|
||||
// We have to keep running swapbuffers here
|
||||
// or else the user's PC will turn into a heater!!
|
||||
SwapBuffers();
|
||||
SwapMainBuffers();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -52,7 +62,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
ClearFramebuffer(Color.Black);
|
||||
|
||||
// Update shared UBOs.
|
||||
_updateUniformConstants(_framebufferSize);
|
||||
_updateUniformConstants(_windowing.MainWindow!.FramebufferSize);
|
||||
|
||||
{
|
||||
CalcScreenMatrices(ScreenSize, out var proj, out var view);
|
||||
@@ -64,7 +74,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
DrawSplash(_renderHandle);
|
||||
FlushRenderQueue();
|
||||
SwapBuffers();
|
||||
SwapMainBuffers();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -82,8 +92,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
TakeScreenshot(ScreenshotType.Final);
|
||||
|
||||
BlitSecondaryWindows();
|
||||
|
||||
// And finally, swap those buffers!
|
||||
SwapBuffers();
|
||||
SwapMainBuffers();
|
||||
}
|
||||
|
||||
private void RenderOverlays(Viewport vp, OverlaySpace space, in Box2 worldBox)
|
||||
@@ -165,16 +177,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
if (oTargets.Count > 0 && ScreenBufferTexture != null)
|
||||
{
|
||||
if (lastFrameSize != _framebufferSize)
|
||||
if (lastFrameSize != texture.Size)
|
||||
{
|
||||
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0,
|
||||
_hasGLSrgb ? PixelInternalFormat.Srgb8Alpha8 : PixelInternalFormat.Rgba8, _framebufferSize.X,
|
||||
_framebufferSize.Y, 0,
|
||||
_hasGLSrgb ? PixelInternalFormat.Srgb8Alpha8 : PixelInternalFormat.Rgba8, texture.Size.X,
|
||||
texture.Size.Y, 0,
|
||||
PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
|
||||
}
|
||||
|
||||
lastFrameSize = _framebufferSize;
|
||||
lastFrameSize = texture.Size;
|
||||
CopyRenderTextureToTexture(texture, ScreenBufferTexture);
|
||||
foreach (Overlay overlay in oTargets)
|
||||
{
|
||||
@@ -338,6 +350,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
_renderHandle.SetProjView(oldProj, oldView);
|
||||
_renderHandle.UseShader(null);
|
||||
|
||||
// TODO: cache this properly across frames.
|
||||
entityPostRenderTarget.DisposeDeferred();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,15 +415,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
handle.DrawingHandleScreen.DrawTexture(texture, (ScreenSize - texture.Size) / 2);
|
||||
}
|
||||
|
||||
private void RenderViewport(Viewport viewport)
|
||||
private void RenderInRenderTarget(RenderTargetBase rt, Action a)
|
||||
{
|
||||
if (viewport.Eye == null || viewport.Eye.Position.MapId == MapId.Nullspace)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var _ = DebugGroup($"Viewport: {viewport.Name}");
|
||||
|
||||
// TODO: for the love of god all this state pushing/popping needs to be cleaned up.
|
||||
|
||||
var oldTransform = _currentMatrixModel;
|
||||
@@ -417,23 +425,50 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Have to flush the render queue so that all commands finish rendering to the previous framebuffer.
|
||||
FlushRenderQueue();
|
||||
|
||||
var eye = viewport.Eye;
|
||||
|
||||
var oldVp = _currentViewport;
|
||||
_currentViewport = viewport;
|
||||
|
||||
var state = PushRenderStateFull();
|
||||
|
||||
{
|
||||
// Actual code that isn't just pushing/popping renderer state so we can return safely.
|
||||
|
||||
var rt = _currentViewport.RenderTarget;
|
||||
BindRenderTargetFull(RtToLoaded(rt));
|
||||
ClearFramebuffer(default);
|
||||
SetViewportImmediate(Box2i.FromDimensions(Vector2i.Zero, rt.Size));
|
||||
_updateUniformConstants(viewport.Size);
|
||||
_updateUniformConstants(rt.Size);
|
||||
CalcScreenMatrices(rt.Size, out var proj, out var view);
|
||||
SetProjViewFull(proj, view);
|
||||
|
||||
CalcWorldMatrices(rt.Size, viewport.RenderScale, eye, out var proj, out var view);
|
||||
// Smugleaf moment
|
||||
a();
|
||||
|
||||
FlushRenderQueue();
|
||||
}
|
||||
|
||||
FenceRenderTarget(rt);
|
||||
|
||||
PopRenderStateFull(state);
|
||||
_updateUniformConstants(_currentRenderTarget.Size);
|
||||
|
||||
SetScissorFull(oldScissor);
|
||||
_currentMatrixModel = oldTransform;
|
||||
}
|
||||
|
||||
private void RenderViewport(Viewport viewport)
|
||||
{
|
||||
if (viewport.Eye == null || viewport.Eye.Position.MapId == MapId.Nullspace)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RenderInRenderTarget(viewport.RenderTarget, () =>
|
||||
{
|
||||
using var _ = DebugGroup($"Viewport: {viewport.Name}");
|
||||
|
||||
var oldVp = _currentViewport;
|
||||
|
||||
_currentViewport = viewport;
|
||||
var eye = viewport.Eye;
|
||||
|
||||
// Actual code that isn't just pushing/popping renderer state so we can return safely.
|
||||
|
||||
CalcWorldMatrices(viewport.RenderTarget.Size, viewport.RenderScale, eye, out var proj, out var view);
|
||||
SetProjViewFull(proj, view);
|
||||
|
||||
// Calculate world-space AABB for camera, to cull off-screen things.
|
||||
@@ -480,7 +515,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// So there are distortions from incorrect projection.
|
||||
_renderHandle.UseShader(_fovDebugShaderInstance);
|
||||
_renderHandle.DrawingHandleScreen.SetTransform(Matrix3.Identity);
|
||||
var pos = UIBox2.FromDimensions(ScreenSize / 2 - (200, 200), (400, 400));
|
||||
var pos = UIBox2.FromDimensions(viewport.Size / 2 - (200, 200), (400, 400));
|
||||
_renderHandle.DrawingHandleScreen.DrawTextureRect(FovTexture, pos);
|
||||
}
|
||||
|
||||
@@ -490,19 +525,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_renderHandle.DrawingHandleScreen.SetTransform(Matrix3.Identity);
|
||||
_renderHandle.DrawingHandleScreen.DrawTextureRect(
|
||||
viewport.WallBleedIntermediateRenderTarget2.Texture,
|
||||
UIBox2.FromDimensions(Vector2.Zero, ScreenSize), new Color(1, 1, 1, 0.5f));
|
||||
UIBox2.FromDimensions(Vector2.Zero, viewport.Size), new Color(1, 1, 1, 0.5f));
|
||||
}
|
||||
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldBounds);
|
||||
FlushRenderQueue();
|
||||
}
|
||||
|
||||
PopRenderStateFull(state);
|
||||
_updateUniformConstants(oldVp?.Size ?? _framebufferSize);
|
||||
|
||||
SetScissorFull(oldScissor);
|
||||
_currentMatrixModel = oldTransform;
|
||||
_currentViewport = oldVp;
|
||||
_currentViewport = oldVp;
|
||||
});
|
||||
}
|
||||
|
||||
private static Box2 CalcWorldBounds(Viewport viewport)
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, _loadedTextures[target.TextureId].OpenGLObject.Handle);
|
||||
CheckGlError();
|
||||
GL.CopyTexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, 0, 0, _framebufferSize.X, _framebufferSize.Y);
|
||||
GL.CopyTexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, 0, 0, source.Size.X, source.Size.Y);
|
||||
CheckGlError();
|
||||
|
||||
if (pause && store != null) {
|
||||
@@ -160,12 +160,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void CheckGlErrorInternal(string? path, int line)
|
||||
private void CheckGlErrorInternal(string? path, int line)
|
||||
{
|
||||
var err = GL.GetError();
|
||||
if (err != ErrorCode.NoError)
|
||||
{
|
||||
Logger.ErrorS("clyde.ogl", $"OpenGL error: {err} at {path}:{line}\n{Environment.StackTrace}");
|
||||
_sawmillOgl.Error($"OpenGL error: {err} at {path}:{line}\n{Environment.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void LoadGLProc<T>(string name, out T field) where T : Delegate
|
||||
{
|
||||
var proc = _graphicsContext.GetProcAddress(name);
|
||||
var proc = _windowing!.GraphicsBindingContext.GetProcAddress(name);
|
||||
if (proc == IntPtr.Zero || proc == new IntPtr(1) || proc == new IntPtr(2))
|
||||
{
|
||||
throw new InvalidOperationException($"Unable to load GL function '{name}'!");
|
||||
|
||||
@@ -1022,13 +1022,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return (w, h);
|
||||
}
|
||||
|
||||
protected override void LightmapDividerChanged(int newValue)
|
||||
private void LightmapDividerChanged(int newValue)
|
||||
{
|
||||
_lightmapDivider = newValue;
|
||||
RegenAllLightRts();
|
||||
}
|
||||
|
||||
protected override void MaxLightsPerSceneChanged(int newValue)
|
||||
private void MaxLightsPerSceneChanged(int newValue)
|
||||
{
|
||||
_maxLightsPerScene = newValue;
|
||||
|
||||
@@ -1047,7 +1047,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
nameof(_shadowRenderTarget));
|
||||
}
|
||||
|
||||
protected override void SoftShadowsChanged(bool newValue)
|
||||
private void SoftShadowsChanged(bool newValue)
|
||||
{
|
||||
_enableSoftShadows = newValue;
|
||||
}
|
||||
|
||||
@@ -112,6 +112,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return clydeTexture;
|
||||
}
|
||||
|
||||
public void RenderInRenderTarget(IRenderTarget target, Action a)
|
||||
{
|
||||
_clyde.RenderInRenderTarget((RenderTargetBase) target, a);
|
||||
}
|
||||
|
||||
public void SetScissor(UIBox2i? scissorBox)
|
||||
{
|
||||
_clyde.DrawSetScissor(scissorBox);
|
||||
|
||||
@@ -24,9 +24,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private readonly ConcurrentQueue<ClydeHandle> _renderTargetDisposeQueue
|
||||
= new();
|
||||
|
||||
IRenderWindow IClyde.MainWindowRenderTarget => _mainWindowRenderTarget;
|
||||
// Initialized in Clyde's constructor
|
||||
private readonly RenderWindow _mainWindowRenderTarget;
|
||||
private readonly RenderMainWindow _mainMainWindowRenderMainTarget;
|
||||
|
||||
// This is always kept up-to-date, except in CreateRenderTarget (because it restores the old value)
|
||||
// It is used for SRGB emulation.
|
||||
@@ -98,7 +97,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
case RTCF.RG32F:
|
||||
case RTCF.R11FG11FB10F:
|
||||
case RTCF.Rgba16F:
|
||||
Logger.WarningS("clyde.ogl", "The framebuffer {0} [{1}] is trying to be floating-point when that's not supported. Forcing Rgba8.", name == null ? "[unnamed]" : name, size);
|
||||
_sawmillOgl.Warning("The framebuffer {0} [{1}] is trying to be floating-point when that's not supported. Forcing Rgba8.", name == null ? "[unnamed]" : name, size);
|
||||
colorFormat = RTCF.Rgba8;
|
||||
break;
|
||||
}
|
||||
@@ -268,10 +267,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateWindowLoadedRtSize()
|
||||
private void UpdateMainWindowLoadedRtSize()
|
||||
{
|
||||
var loadedRt = RtToLoaded(_mainWindowRenderTarget);
|
||||
loadedRt.Size = _framebufferSize;
|
||||
var loadedRt = RtToLoaded(_mainMainWindowRenderMainTarget);
|
||||
loadedRt.Size = _windowing!.MainWindow!.FramebufferSize;
|
||||
}
|
||||
|
||||
private sealed class LoadedRenderTarget
|
||||
@@ -299,6 +298,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
protected readonly Clyde Clyde;
|
||||
private bool _disposed;
|
||||
|
||||
public bool MakeGLFence;
|
||||
public nint LastGLSync;
|
||||
|
||||
protected RenderTargetBase(Clyde clyde, ClydeHandle handle)
|
||||
{
|
||||
Clyde = clyde;
|
||||
@@ -330,6 +332,23 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void DisposeDeferred()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
DisposeDeferredImpl();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void DisposeDeferredImpl()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
~RenderTargetBase()
|
||||
{
|
||||
Dispose(false);
|
||||
@@ -357,16 +376,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
else
|
||||
{
|
||||
Clyde._renderTargetDisposeQueue.Enqueue(Handle);
|
||||
DisposeDeferredImpl();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisposeDeferredImpl()
|
||||
{
|
||||
Clyde._renderTargetDisposeQueue.Enqueue(Handle);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RenderWindow : RenderTargetBase, IRenderWindow
|
||||
private sealed class RenderMainWindow : RenderTargetBase
|
||||
{
|
||||
public override Vector2i Size => Clyde._framebufferSize;
|
||||
public override Vector2i Size => Clyde._windowing!.MainWindow!.FramebufferSize;
|
||||
|
||||
public RenderWindow(Clyde clyde, ClydeHandle handle) : base(clyde, handle)
|
||||
public RenderMainWindow(Clyde clyde, ClydeHandle handle) : base(clyde, handle)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Runtime.InteropServices;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
@@ -116,7 +117,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
view = Matrix3.Identity;
|
||||
}
|
||||
|
||||
private static void CalcWorldMatrices(in Vector2i screenSize, in Vector2 renderScale, IEye eye, out Matrix3 proj, out Matrix3 view)
|
||||
private static void CalcWorldMatrices(in Vector2i screenSize, in Vector2 renderScale, IEye eye,
|
||||
out Matrix3 proj, out Matrix3 view)
|
||||
{
|
||||
eye.GetViewMatrix(out view, renderScale);
|
||||
|
||||
@@ -262,8 +264,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void _drawQuad(Vector2 a, Vector2 b, in Matrix3 modelMatrix, GLShaderProgram program)
|
||||
{
|
||||
BindVertexArray(QuadVAO.Handle);
|
||||
DrawQuadWithVao(QuadVAO, a, b, modelMatrix, program);
|
||||
}
|
||||
|
||||
private void DrawQuadWithVao(GLHandle vao, Vector2 a, Vector2 b, in Matrix3 modelMatrix,
|
||||
GLShaderProgram program)
|
||||
{
|
||||
BindVertexArray(vao.Handle);
|
||||
CheckGlError();
|
||||
|
||||
var rectTransform = Matrix3.Identity;
|
||||
(rectTransform.R0C0, rectTransform.R1C1) = b - a;
|
||||
(rectTransform.R0C2, rectTransform.R1C2) = a;
|
||||
@@ -419,8 +428,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
//It's important to start at Texture6 here since DrawCommandBatch uses Texture0 and Texture1 immediately after calling this
|
||||
//function! If passing in textures as uniforms ever stops working it might be since someone made it use all the way up to Texture6 too.
|
||||
//Might change this in the future?
|
||||
TextureUnit cTarget = TextureUnit.Texture6+textureUnitVal;
|
||||
SetTexture(cTarget, ((ClydeTexture)clydeTexture).TextureId);
|
||||
TextureUnit cTarget = TextureUnit.Texture6 + textureUnitVal;
|
||||
SetTexture(cTarget, ((ClydeTexture) clydeTexture).TextureId);
|
||||
program.SetUniformTexture(name, cTarget);
|
||||
textureUnitVal++;
|
||||
break;
|
||||
@@ -818,7 +827,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_lightingReady = false;
|
||||
_currentMatrixModel = Matrix3.Identity;
|
||||
SetScissorFull(null);
|
||||
BindRenderTargetFull(_mainWindowRenderTarget);
|
||||
BindRenderTargetFull(_mainMainWindowRenderMainTarget);
|
||||
_batchMetaData = null;
|
||||
_queuedShader = _defaultShader.Handle;
|
||||
}
|
||||
@@ -832,6 +841,109 @@ namespace Robust.Client.Graphics.Clyde
|
||||
BlendingFactorDest.OneMinusSrcAlpha);
|
||||
}
|
||||
|
||||
private void BlitSecondaryWindows()
|
||||
{
|
||||
// Only got main window.
|
||||
if (_windowing!.AllWindows.Count == 1)
|
||||
return;
|
||||
|
||||
if (!_hasGLFenceSync && _cfg.GetCVar(CVars.DisplayForceSyncWindows))
|
||||
{
|
||||
GL.Finish();
|
||||
}
|
||||
|
||||
if (EffectiveThreadWindowBlit)
|
||||
{
|
||||
foreach (var window in _windowing.AllWindows)
|
||||
{
|
||||
if (window.IsMainWindow)
|
||||
continue;
|
||||
|
||||
window.BlitDoneEvent!.Reset();
|
||||
window.BlitStartEvent!.Set();
|
||||
window.BlitDoneEvent.Wait();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var window in _windowing.AllWindows)
|
||||
{
|
||||
if (window.IsMainWindow)
|
||||
continue;
|
||||
|
||||
_windowing.GLMakeContextCurrent(window);
|
||||
BlitThreadDoSecondaryWindowBlit(window);
|
||||
}
|
||||
|
||||
_windowing.GLMakeContextCurrent(_windowing.MainWindow!);
|
||||
}
|
||||
}
|
||||
|
||||
private void BlitThreadDoSecondaryWindowBlit(WindowReg window)
|
||||
{
|
||||
var rt = window.RenderTexture!;
|
||||
|
||||
if (_hasGLFenceSync)
|
||||
{
|
||||
// 0xFFFFFFFFFFFFFFFFUL is GL_TIMEOUT_IGNORED
|
||||
var sync = rt!.LastGLSync;
|
||||
GL.WaitSync(sync, WaitSyncFlags.None, unchecked((long) 0xFFFFFFFFFFFFFFFFUL));
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
GL.Viewport(0, 0, window.FramebufferSize.X, window.FramebufferSize.Y);
|
||||
CheckGlError();
|
||||
|
||||
SetTexture(TextureUnit.Texture0, window.RenderTexture!.Texture);
|
||||
|
||||
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
|
||||
CheckGlError();
|
||||
|
||||
window.BlitDoneEvent?.Set();
|
||||
_windowing!.WindowSwapBuffers(window);
|
||||
}
|
||||
|
||||
private void BlitThreadInit(WindowReg reg)
|
||||
{
|
||||
_windowing!.GLMakeContextCurrent(reg);
|
||||
_windowing.GLSwapInterval(0);
|
||||
|
||||
if (!_isGLES)
|
||||
GL.Enable(EnableCap.FramebufferSrgb);
|
||||
|
||||
var vao = GL.GenVertexArray();
|
||||
GL.BindVertexArray(vao);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, WindowVBO.ObjectHandle);
|
||||
// Vertex Coords
|
||||
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 0);
|
||||
GL.EnableVertexAttribArray(0);
|
||||
// Texture Coords.
|
||||
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 2 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(1);
|
||||
|
||||
var program = _compileProgram(_winBlitShaderVert, _winBlitShaderFrag, new (string, uint)[]
|
||||
{
|
||||
("aPos", 0),
|
||||
("tCoord", 1),
|
||||
}, includeLib: false);
|
||||
|
||||
GL.UseProgram(program.Handle);
|
||||
var loc = GL.GetUniformLocation(program.Handle, "tex");
|
||||
SetTexture(TextureUnit.Texture0, reg.RenderTexture!.Texture);
|
||||
GL.Uniform1(loc, 0);
|
||||
}
|
||||
|
||||
private void FenceRenderTarget(RenderTargetBase rt)
|
||||
{
|
||||
if (!_hasGLFenceSync || !rt.MakeGLFence)
|
||||
return;
|
||||
|
||||
if (rt.LastGLSync != 0)
|
||||
GL.DeleteSync(rt.LastGLSync);
|
||||
|
||||
rt.LastGLSync = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct RenderCommand
|
||||
{
|
||||
|
||||
@@ -86,12 +86,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_ => throw new ArgumentException("Unsupported pixel type.")
|
||||
};
|
||||
|
||||
var size = ClampSubRegion(fbSize, subRegion);
|
||||
var size = ClydeBase.ClampSubRegion(fbSize, subRegion);
|
||||
|
||||
var bufferLength = size.X * size.Y;
|
||||
if (!(_hasGLFenceSync && HasGLAnyMapBuffer && _hasGLPixelBufferObjects))
|
||||
{
|
||||
Logger.DebugS("clyde.ogl",
|
||||
_sawmillOgl.Debug("clyde.ogl",
|
||||
"Necessary features for async screenshots not available, falling back to blocking path.");
|
||||
|
||||
// We need these 3 features to be able to do asynchronous screenshots, if we don't have them,
|
||||
|
||||
@@ -23,6 +23,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private string _shaderWrapCodeRawFrag = default!;
|
||||
private string _shaderWrapCodeRawVert = default!;
|
||||
|
||||
private string _winBlitShaderVert = default!;
|
||||
private string _winBlitShaderFrag = default!;
|
||||
|
||||
private readonly Dictionary<ClydeHandle, LoadedShader> _loadedShaders =
|
||||
new();
|
||||
|
||||
@@ -117,6 +120,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_shaderWrapCodeRawVert = ReadEmbeddedShader("base-raw.vert");
|
||||
_shaderWrapCodeRawFrag = ReadEmbeddedShader("base-raw.frag");
|
||||
|
||||
_winBlitShaderVert = ReadEmbeddedShader("winblit.vert");
|
||||
_winBlitShaderFrag = ReadEmbeddedShader("winblit.frag");
|
||||
|
||||
var defaultLoadedShader = _resourceCache
|
||||
.GetResource<ShaderSourceResource>("/Shaders/Internal/default-sprite.swsl").ClydeHandle;
|
||||
|
||||
@@ -135,7 +141,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
private GLShaderProgram _compileProgram(string vertexSource, string fragmentSource,
|
||||
(string, uint)[] attribLocations, string? name = null)
|
||||
(string, uint)[] attribLocations, string? name = null, bool includeLib=true)
|
||||
{
|
||||
GLShader? vertexShader = null;
|
||||
GLShader? fragmentShader = null;
|
||||
@@ -172,8 +178,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
versionHeader += "#define HAS_UNIFORM_BUFFERS\n";
|
||||
}
|
||||
|
||||
vertexSource = versionHeader + "#define VERTEX_SHADER\n" + _shaderLibrary + vertexSource;
|
||||
fragmentSource = versionHeader + "#define FRAGMENT_SHADER\n" + _shaderLibrary + fragmentSource;
|
||||
var lib = includeLib ? _shaderLibrary : "";
|
||||
vertexSource = versionHeader + "#define VERTEX_SHADER\n" + lib + vertexSource;
|
||||
fragmentSource = versionHeader + "#define FRAGMENT_SHADER\n" + lib + fragmentSource;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public OwnedTexture LoadTextureFromPNGStream(Stream stream, string? name = null,
|
||||
TextureLoadParameters? loadParams = null)
|
||||
{
|
||||
DebugTools.Assert(_mainThread == Thread.CurrentThread);
|
||||
DebugTools.Assert(_gameThread == Thread.CurrentThread);
|
||||
|
||||
// Load using Rgba32.
|
||||
using var image = Image.Load<Rgba32>(stream);
|
||||
@@ -40,7 +40,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public OwnedTexture LoadTextureFromImage<T>(Image<T> image, string? name = null,
|
||||
TextureLoadParameters? loadParams = null) where T : unmanaged, IPixel<T>
|
||||
{
|
||||
DebugTools.Assert(_mainThread == Thread.CurrentThread);
|
||||
DebugTools.Assert(_gameThread == Thread.CurrentThread);
|
||||
|
||||
var actualParams = loadParams ?? TextureLoadParameters.Default;
|
||||
var pixelType = typeof(T);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,11 +2,13 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
@@ -19,7 +21,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
/// <summary>
|
||||
/// Responsible for most things rendering on OpenGL mode.
|
||||
/// </summary>
|
||||
internal sealed partial class Clyde : ClydeBase, IClydeInternal, IClydeAudio, IPostInjectInit
|
||||
internal sealed partial class Clyde : IClydeInternal, IClydeAudio, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IClydeTileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
@@ -31,6 +33,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
[Dependency] private readonly IUserInterfaceManagerInternal _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private GLUniformBuffer<ProjViewMatrices> ProjViewUBO = default!;
|
||||
private GLUniformBuffer<UniformConstants> UniformConstantsUBO = default!;
|
||||
@@ -43,6 +46,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private GLBuffer QuadVBO = default!;
|
||||
private GLHandle QuadVAO;
|
||||
|
||||
// VBO to blit to the window
|
||||
// VAO is per-window and not stored (not necessary!)
|
||||
private GLBuffer WindowVBO = default!;
|
||||
|
||||
private bool _drawingSplash = true;
|
||||
|
||||
private GLShaderProgram? _currentProgram;
|
||||
@@ -53,11 +60,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private bool _checkGLErrors;
|
||||
|
||||
private Thread? _gameThread;
|
||||
|
||||
private ISawmill _sawmillOgl = default!;
|
||||
|
||||
public Clyde()
|
||||
{
|
||||
// Init main window render target.
|
||||
var windowRid = AllocRid();
|
||||
var window = new RenderWindow(this, windowRid);
|
||||
var window = new RenderMainWindow(this, windowRid);
|
||||
var loadedData = new LoadedRenderTarget
|
||||
{
|
||||
IsWindow = true,
|
||||
@@ -65,33 +76,54 @@ namespace Robust.Client.Graphics.Clyde
|
||||
};
|
||||
_renderTargets.Add(windowRid, loadedData);
|
||||
|
||||
_mainWindowRenderTarget = window;
|
||||
_mainMainWindowRenderMainTarget = window;
|
||||
_currentRenderTarget = RtToLoaded(window);
|
||||
_currentBoundRenderTarget = _currentRenderTarget;
|
||||
}
|
||||
|
||||
public override bool Initialize()
|
||||
public bool InitializePreWindowing()
|
||||
{
|
||||
base.Initialize();
|
||||
_sawmillOgl = Logger.GetSawmill("clyde.ogl");
|
||||
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
|
||||
_cfg.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
|
||||
_cfg.OnValueChanged(CVars.DisplayVSync, VSyncChanged, true);
|
||||
_cfg.OnValueChanged(CVars.DisplayWindowMode, WindowModeChanged, true);
|
||||
_cfg.OnValueChanged(CVars.DisplayLightMapDivider, LightmapDividerChanged, true);
|
||||
_cfg.OnValueChanged(CVars.DisplayMaxLightsPerScene, MaxLightsPerSceneChanged, true);
|
||||
_cfg.OnValueChanged(CVars.DisplaySoftShadows, SoftShadowsChanged, true);
|
||||
// I can't be bothered to tear down and set these threads up in a cvar change handler.
|
||||
_threadWindowBlit = _cfg.GetCVar(CVars.DisplayThreadWindowBlit);
|
||||
|
||||
if (!InitWindowing())
|
||||
{
|
||||
return InitWindowing();
|
||||
}
|
||||
|
||||
public bool InitializePostWindowing()
|
||||
{
|
||||
_gameThread = Thread.CurrentThread;
|
||||
if (!InitMainWindowAndRenderer())
|
||||
return false;
|
||||
}
|
||||
|
||||
_initializeAudio();
|
||||
ReloadConfig();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SeparateWindowThread => true;
|
||||
|
||||
public void EnterWindowLoop()
|
||||
{
|
||||
_windowing!.EnterWindowLoop();
|
||||
}
|
||||
|
||||
public void TerminateWindowLoop()
|
||||
{
|
||||
_windowing!.TerminateWindowLoop();
|
||||
}
|
||||
|
||||
public void FrameProcess(FrameEventArgs eventArgs)
|
||||
{
|
||||
_updateAudio();
|
||||
|
||||
FlushCursorDispose();
|
||||
_windowing?.FlushDispose();
|
||||
FlushShaderInstanceDispose();
|
||||
FlushRenderTargetDispose();
|
||||
FlushTextureDispose();
|
||||
@@ -108,21 +140,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public IClydeDebugInfo DebugInfo { get; private set; } = default!;
|
||||
public IClydeDebugStats DebugStats => _debugStats;
|
||||
|
||||
protected override void ReadConfig()
|
||||
{
|
||||
base.ReadConfig();
|
||||
_lightmapDivider = ConfigurationManager.GetCVar(CVars.DisplayLightMapDivider);
|
||||
_maxLightsPerScene = ConfigurationManager.GetCVar(CVars.DisplayMaxLightsPerScene);
|
||||
_enableSoftShadows = ConfigurationManager.GetCVar(CVars.DisplaySoftShadows);
|
||||
}
|
||||
|
||||
protected override void ReloadConfig()
|
||||
{
|
||||
base.ReloadConfig();
|
||||
|
||||
RegenAllLightRts();
|
||||
}
|
||||
|
||||
public void PostInject()
|
||||
{
|
||||
_mapManager.TileChanged += _updateTileMapOnUpdate;
|
||||
@@ -135,10 +152,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
RegisterBlockCVars();
|
||||
}
|
||||
|
||||
public override event Action<WindowResizedEventArgs>? OnWindowResized;
|
||||
|
||||
public override event Action<WindowFocusedEventArgs>? OnWindowFocused;
|
||||
|
||||
private void InitOpenGL()
|
||||
{
|
||||
var vendor = GL.GetString(StringName.Vendor);
|
||||
@@ -147,16 +160,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var major = GL.GetInteger(GetPName.MajorVersion);
|
||||
var minor = GL.GetInteger(GetPName.MinorVersion);
|
||||
|
||||
Logger.DebugS("clyde.ogl", "OpenGL Vendor: {0}", vendor);
|
||||
Logger.DebugS("clyde.ogl", "OpenGL Renderer: {0}", renderer);
|
||||
Logger.DebugS("clyde.ogl", "OpenGL Version: {0}", version);
|
||||
_sawmillOgl.Debug("OpenGL Vendor: {0}", vendor);
|
||||
_sawmillOgl.Debug("OpenGL Renderer: {0}", renderer);
|
||||
_sawmillOgl.Debug("OpenGL Version: {0}", version);
|
||||
|
||||
var overrideVersion = ParseGLOverrideVersion();
|
||||
|
||||
if (overrideVersion != null)
|
||||
{
|
||||
(major, minor) = overrideVersion.Value;
|
||||
Logger.DebugS("clyde.ogl", "OVERRIDING detected GL version to: {0}.{1}", major, minor);
|
||||
_sawmillOgl.Debug("OVERRIDING detected GL version to: {0}.{1}", major, minor);
|
||||
}
|
||||
|
||||
DetectOpenGLFeatures(major, minor);
|
||||
@@ -183,7 +196,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
if (!HasGLAnyVertexArrayObjects)
|
||||
{
|
||||
Logger.WarningS("clyde.ogl", "NO VERTEX ARRAY OBJECTS! Things will probably go terribly, terribly wrong (no fallback path yet)");
|
||||
_sawmillOgl.Warning("NO VERTEX ARRAY OBJECTS! Things will probably go terribly, terribly wrong (no fallback path yet)");
|
||||
}
|
||||
|
||||
ResetBlendFunc();
|
||||
@@ -193,23 +206,23 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Primitive Restart's presence or lack thereof changes the amount of required memory.
|
||||
InitRenderingBatchBuffers();
|
||||
|
||||
Logger.DebugS("clyde.ogl", "Loading stock textures...");
|
||||
_sawmillOgl.Debug("Loading stock textures...");
|
||||
|
||||
LoadStockTextures();
|
||||
|
||||
Logger.DebugS("clyde.ogl", "Loading stock shaders...");
|
||||
_sawmillOgl.Debug("Loading stock shaders...");
|
||||
|
||||
LoadStockShaders();
|
||||
|
||||
Logger.DebugS("clyde.ogl", "Creating various GL objects...");
|
||||
_sawmillOgl.Debug("Creating various GL objects...");
|
||||
|
||||
CreateMiscGLObjects();
|
||||
|
||||
Logger.DebugS("clyde.ogl", "Setting up RenderHandle...");
|
||||
_sawmillOgl.Debug("Setting up RenderHandle...");
|
||||
|
||||
_renderHandle = new RenderHandle(this);
|
||||
|
||||
Logger.DebugS("clyde.ogl", "Setting viewport and rendering splash...");
|
||||
_sawmillOgl.Debug("Setting viewport and rendering splash...");
|
||||
|
||||
GL.Viewport(0, 0, ScreenSize.X, ScreenSize.Y);
|
||||
CheckGlError();
|
||||
@@ -220,7 +233,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private (int major, int minor)? ParseGLOverrideVersion()
|
||||
{
|
||||
var overrideGLVersion = ConfigurationManager.GetCVar(CVars.DisplayOGLOverrideVersion);
|
||||
var overrideGLVersion = _cfg.GetCVar(CVars.DisplayOGLOverrideVersion);
|
||||
if (string.IsNullOrEmpty(overrideGLVersion))
|
||||
{
|
||||
return null;
|
||||
@@ -229,14 +242,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var split = overrideGLVersion.Split(".");
|
||||
if (split.Length != 2)
|
||||
{
|
||||
Logger.WarningS("clyde.ogl", "display.ogl_override_version is in invalid format");
|
||||
_sawmillOgl.Warning("display.ogl_override_version is in invalid format");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!int.TryParse(split[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var major)
|
||||
|| !int.TryParse(split[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var minor))
|
||||
{
|
||||
Logger.WarningS("clyde.ogl", "display.ogl_override_version is in invalid format");
|
||||
_sawmillOgl.Warning("display.ogl_override_version is in invalid format");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -259,15 +272,27 @@ namespace Robust.Client.Graphics.Clyde
|
||||
quadVertices,
|
||||
nameof(QuadVBO));
|
||||
|
||||
QuadVAO = new GLHandle(GenVertexArray());
|
||||
BindVertexArray(QuadVAO.Handle);
|
||||
ObjectLabelMaybe(ObjectLabelIdentifier.VertexArray, QuadVAO, nameof(QuadVAO));
|
||||
// Vertex Coords
|
||||
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 0);
|
||||
GL.EnableVertexAttribArray(0);
|
||||
// Texture Coords.
|
||||
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 2 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(1);
|
||||
QuadVAO = MakeQuadVao();
|
||||
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
// Window VBO
|
||||
{
|
||||
Span<Vertex2D> winVertices = stackalloc[]
|
||||
{
|
||||
new Vertex2D(-1, 1, 0, 1),
|
||||
new Vertex2D(-1, -1, 0, 0),
|
||||
new Vertex2D(1, 1, 1, 1),
|
||||
new Vertex2D(1, -1, 1, 0),
|
||||
};
|
||||
|
||||
WindowVBO = new GLBuffer<Vertex2D>(
|
||||
this,
|
||||
BufferTarget.ArrayBuffer,
|
||||
BufferUsageHint.StaticDraw,
|
||||
winVertices,
|
||||
nameof(WindowVBO));
|
||||
|
||||
CheckGlError();
|
||||
}
|
||||
@@ -299,7 +324,23 @@ namespace Robust.Client.Graphics.Clyde
|
||||
screenBufferHandle = new GLHandle(GL.GenTexture());
|
||||
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
|
||||
ApplySampleParameters(TextureSampleParameters.Default);
|
||||
ScreenBufferTexture = GenTexture(screenBufferHandle, _framebufferSize, true, null, TexturePixelType.Rgba32);
|
||||
ScreenBufferTexture = GenTexture(screenBufferHandle, _windowing!.MainWindow!.FramebufferSize, true, null, TexturePixelType.Rgba32);
|
||||
}
|
||||
|
||||
private GLHandle MakeQuadVao()
|
||||
{
|
||||
var vao = new GLHandle(GenVertexArray());
|
||||
BindVertexArray(vao.Handle);
|
||||
ObjectLabelMaybe(ObjectLabelIdentifier.VertexArray, vao, nameof(QuadVAO));
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, QuadVBO.ObjectHandle);
|
||||
// Vertex Coords
|
||||
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 0);
|
||||
GL.EnableVertexAttribArray(0);
|
||||
// Texture Coords.
|
||||
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 2 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(1);
|
||||
|
||||
return vao;
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
@@ -307,7 +348,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
if (!_hasGLKhrDebug)
|
||||
{
|
||||
Logger.DebugS("clyde.ogl", "KHR_debug not present, OpenGL debug logging not enabled.");
|
||||
_sawmillOgl.Debug("KHR_debug not present, OpenGL debug logging not enabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Input;
|
||||
@@ -18,13 +19,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
/// Hey look, it's Clyde's evil twin brother!
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
internal sealed class ClydeHeadless : ClydeBase, IClydeInternal, IClydeAudio
|
||||
internal sealed class ClydeHeadless : IClydeInternal, IClydeAudio
|
||||
{
|
||||
// Would it make sense to report a fake resolution like 720p here so code doesn't break? idk.
|
||||
public IRenderWindow MainWindowRenderTarget { get; }
|
||||
public override Vector2i ScreenSize { get; } = (1280, 720);
|
||||
public IClydeWindow MainWindow { get; }
|
||||
public Vector2i ScreenSize => (1280, 720);
|
||||
public IEnumerable<IClydeWindow> AllWindows => _windows;
|
||||
public Vector2 DefaultWindowScale => (1, 1);
|
||||
public override bool IsFocused => true;
|
||||
public bool IsFocused => true;
|
||||
private readonly List<IClydeWindow> _windows = new();
|
||||
private int _nextWindowId = 2;
|
||||
|
||||
public ShaderInstance InstanceShader(ClydeHandle handle)
|
||||
{
|
||||
@@ -33,19 +37,25 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public ClydeHeadless()
|
||||
{
|
||||
MainWindowRenderTarget = new DummyRenderWindow(this);
|
||||
var mainRt = new DummyRenderWindow(this);
|
||||
var window = new DummyWindow(mainRt) {Id = new WindowId(1)};
|
||||
|
||||
_windows.Add(window);
|
||||
MainWindow = window;
|
||||
}
|
||||
|
||||
public Vector2 MouseScreenPosition => ScreenSize / 2;
|
||||
public ScreenCoordinates MouseScreenPosition => default;
|
||||
public IClydeDebugInfo DebugInfo { get; } = new DummyDebugInfo();
|
||||
public IClydeDebugStats DebugStats { get; } = new DummyDebugStats();
|
||||
|
||||
public event Action<TextEventArgs>? TextEntered;
|
||||
public event Action<MouseMoveEventArgs>? MouseMove;
|
||||
public event Action<KeyEventArgs>? KeyUp;
|
||||
public event Action<KeyEventArgs>? KeyDown;
|
||||
public event Action<MouseWheelEventArgs>? MouseWheel;
|
||||
public event Action<string>? CloseWindow;
|
||||
public event Action<TextEventArgs>? TextEntered { add { } remove { } }
|
||||
public event Action<MouseMoveEventArgs>? MouseMove { add { } remove { } }
|
||||
public event Action<MouseEnterLeaveEventArgs>? MouseEnterLeave { add { } remove { } }
|
||||
public event Action<KeyEventArgs>? KeyUp { add { } remove { } }
|
||||
public event Action<KeyEventArgs>? KeyDown { add { } remove { } }
|
||||
public event Action<MouseWheelEventArgs>? MouseWheel { add { } remove { } }
|
||||
public event Action<WindowClosedEventArgs>? CloseWindow { add { } remove { } }
|
||||
public event Action<WindowDestroyedEventArgs>? DestroyWindow { add { } remove { } }
|
||||
|
||||
public Texture GetStockTexture(ClydeStockTexture stockTexture)
|
||||
{
|
||||
@@ -55,8 +65,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public ClydeDebugLayers DebugLayers { get; set; }
|
||||
|
||||
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()
|
||||
{
|
||||
@@ -68,7 +76,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void SetWindowTitle(string title)
|
||||
public void SetWindowTitle(string title)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
@@ -83,25 +91,19 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public override bool Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override event Action<WindowResizedEventArgs> OnWindowResized
|
||||
public event Action<WindowResizedEventArgs> OnWindowResized
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public override event Action<WindowFocusedEventArgs> OnWindowFocused
|
||||
public event Action<WindowFocusedEventArgs> OnWindowFocused
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event Action OnWindowScaleChanged
|
||||
public event Action<WindowContentScaleEventArgs> OnWindowScaleChanged
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
@@ -122,6 +124,28 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public bool SeparateWindowThread => false;
|
||||
|
||||
public bool InitializePreWindowing()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void TerminateWindowLoop()
|
||||
{
|
||||
throw new InvalidOperationException("ClydeHeadless does not use windowing threads");
|
||||
}
|
||||
|
||||
public void EnterWindowLoop()
|
||||
{
|
||||
throw new InvalidOperationException("ClydeHeadless does not use windowing threads");
|
||||
}
|
||||
|
||||
public bool InitializePostWindowing()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public OwnedTexture LoadTextureFromPNGStream(Stream stream, string? name = null,
|
||||
TextureLoadParameters? loadParams = null)
|
||||
{
|
||||
@@ -170,7 +194,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public void Screenshot(ScreenshotType type, CopyPixelsDelegate<Rgb24> callback, UIBox2i? subRegion = null)
|
||||
{
|
||||
// Immediately call callback with an empty buffer.
|
||||
var (x, y) = ClampSubRegion(ScreenSize, subRegion);
|
||||
var (x, y) = ClydeBase.ClampSubRegion(ScreenSize, subRegion);
|
||||
callback(new Image<Rgb24>(x, y));
|
||||
}
|
||||
|
||||
@@ -186,6 +210,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
yield break;
|
||||
}
|
||||
|
||||
public Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters)
|
||||
{
|
||||
var window = new DummyWindow(CreateRenderTarget((123, 123), default))
|
||||
{
|
||||
Id = new WindowId(_nextWindowId++)
|
||||
};
|
||||
_windows.Add(window);
|
||||
|
||||
return Task.FromResult<IClydeWindow>(window);
|
||||
}
|
||||
|
||||
public ClydeHandle LoadShader(ParsedShader shader, string? name = null)
|
||||
{
|
||||
return default;
|
||||
@@ -213,6 +248,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return new(default, default, 1, name);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioRaw(short[] samples, int channels, int sampleRate)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IClydeAudioSource CreateAudioSource(AudioStream stream)
|
||||
{
|
||||
return DummyAudioSource.Instance;
|
||||
@@ -223,9 +263,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return DummyBufferedAudioSource.Instance;
|
||||
}
|
||||
|
||||
public string GetText()
|
||||
public Task<string> GetText()
|
||||
{
|
||||
return string.Empty;
|
||||
return Task.FromResult(string.Empty);
|
||||
}
|
||||
|
||||
public void SetText(string text)
|
||||
@@ -302,6 +342,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetVolumeDirect(float masterVolumeDecay)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DummyBufferedAudioSource : DummyAudioSource, IClydeBufferedAudioSource
|
||||
@@ -440,7 +485,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public void CopyPixelsToMemory<T>(CopyPixelsDelegate<T> callback, UIBox2i? subRegion) where T : unmanaged, IPixel<T>
|
||||
{
|
||||
var (x, y) = ClampSubRegion(Size, subRegion);
|
||||
var (x, y) = ClydeBase.ClampSubRegion(Size, subRegion);
|
||||
callback(new Image<T>(x, y));
|
||||
}
|
||||
|
||||
@@ -451,7 +496,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DummyRenderWindow : IRenderWindow
|
||||
private sealed class DummyRenderWindow : IRenderTarget
|
||||
{
|
||||
private readonly ClydeHeadless _clyde;
|
||||
|
||||
@@ -464,7 +509,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public void CopyPixelsToMemory<T>(CopyPixelsDelegate<T> callback, UIBox2i? subRegion) where T : unmanaged, IPixel<T>
|
||||
{
|
||||
var (x, y) = ClampSubRegion(Size, subRegion);
|
||||
var (x, y) = ClydeBase.ClampSubRegion(Size, subRegion);
|
||||
callback(new Image<T>(x, y));
|
||||
}
|
||||
|
||||
@@ -541,5 +586,34 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Nada
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DummyWindow : IClydeWindow
|
||||
{
|
||||
public DummyWindow(IRenderTarget renderTarget)
|
||||
{
|
||||
RenderTarget = renderTarget;
|
||||
}
|
||||
|
||||
public Vector2i Size { get; } = default;
|
||||
public bool IsDisposed { get; private set; }
|
||||
public WindowId Id { get; set; }
|
||||
public IRenderTarget RenderTarget { get; }
|
||||
public string Title { get; set; } = "";
|
||||
public bool IsFocused => false;
|
||||
public bool IsMinimized => false;
|
||||
public bool IsVisible { get; set; } = true;
|
||||
public Vector2 ContentScale => Vector2.One;
|
||||
public bool DisposeOnClose { get; set; }
|
||||
public event Action<WindowClosedEventArgs>? Closed { add { } remove { } }
|
||||
|
||||
public void MaximizeOnMonitor(IClydeMonitor monitor)
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
private readonly sbyte?[] _uniformIntCache = new sbyte?[UniCount];
|
||||
private readonly Dictionary<string, int> _uniformCache = new();
|
||||
private uint _handle = 0;
|
||||
public uint Handle = 0;
|
||||
private GLShader? _fragmentShader;
|
||||
private GLShader? _vertexShader;
|
||||
public string? Name { get; }
|
||||
@@ -49,22 +49,22 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public void Link((string, uint)[] attribLocations)
|
||||
{
|
||||
ClearCaches();
|
||||
_handle = (uint) GL.CreateProgram();
|
||||
Handle = (uint) GL.CreateProgram();
|
||||
_clyde.CheckGlError();
|
||||
if (Name != null)
|
||||
{
|
||||
_clyde.ObjectLabelMaybe(ObjectLabelIdentifier.Program, _handle, Name);
|
||||
_clyde.ObjectLabelMaybe(ObjectLabelIdentifier.Program, Handle, Name);
|
||||
}
|
||||
|
||||
if (_vertexShader != null)
|
||||
{
|
||||
GL.AttachShader(_handle, _vertexShader.ObjectHandle);
|
||||
GL.AttachShader(Handle, _vertexShader.ObjectHandle);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
if (_fragmentShader != null)
|
||||
{
|
||||
GL.AttachShader(_handle, _fragmentShader.ObjectHandle);
|
||||
GL.AttachShader(Handle, _fragmentShader.ObjectHandle);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
@@ -74,45 +74,50 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// So we have to manually do it here.
|
||||
// Ugh.
|
||||
|
||||
GL.BindAttribLocation(_handle, loc, varName);
|
||||
GL.BindAttribLocation(Handle, loc, varName);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
GL.LinkProgram(_handle);
|
||||
GL.LinkProgram(Handle);
|
||||
_clyde.CheckGlError();
|
||||
|
||||
GL.GetProgram(_handle, GetProgramParameterName.LinkStatus, out var compiled);
|
||||
GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out var compiled);
|
||||
_clyde.CheckGlError();
|
||||
if (compiled != 1)
|
||||
{
|
||||
throw new ShaderCompilationException(GL.GetProgramInfoLog((int) _handle));
|
||||
throw new ShaderCompilationException(GL.GetProgramInfoLog((int) Handle));
|
||||
}
|
||||
}
|
||||
|
||||
public void Use()
|
||||
{
|
||||
DebugTools.Assert(_handle != 0);
|
||||
|
||||
if (_clyde._currentProgram == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ForceUse();
|
||||
}
|
||||
|
||||
public void ForceUse()
|
||||
{
|
||||
DebugTools.Assert(Handle != 0);
|
||||
|
||||
_clyde._currentProgram = this;
|
||||
GL.UseProgram(_handle);
|
||||
GL.UseProgram(Handle);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
if (_handle == 0)
|
||||
if (Handle == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GL.DeleteProgram(_handle);
|
||||
GL.DeleteProgram(Handle);
|
||||
_clyde.CheckGlError();
|
||||
_handle = 0;
|
||||
Handle = 0;
|
||||
}
|
||||
|
||||
public int GetUniform(string name)
|
||||
@@ -137,14 +142,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public bool TryGetUniform(string name, out int index)
|
||||
{
|
||||
DebugTools.Assert(_handle != 0);
|
||||
DebugTools.Assert(Handle != 0);
|
||||
|
||||
if (_uniformCache.TryGetValue(name, out index))
|
||||
{
|
||||
return index != -1;
|
||||
}
|
||||
|
||||
index = GL.GetUniformLocation(_handle, name);
|
||||
index = GL.GetUniformLocation(Handle, name);
|
||||
_clyde.CheckGlError();
|
||||
_uniformCache.Add(name, index);
|
||||
return index != -1;
|
||||
@@ -152,7 +157,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public bool TryGetUniform(int id, out int index)
|
||||
{
|
||||
DebugTools.Assert(_handle != 0);
|
||||
DebugTools.Assert(Handle != 0);
|
||||
DebugTools.Assert(id < UniCount);
|
||||
|
||||
var value = _uniformIntCache[id];
|
||||
@@ -192,7 +197,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
index = GL.GetUniformLocation(_handle, name);
|
||||
index = GL.GetUniformLocation(Handle, name);
|
||||
_clyde.CheckGlError();
|
||||
_uniformIntCache[id] = (sbyte)index;
|
||||
return index != -1;
|
||||
@@ -203,9 +208,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public void BindBlock(string blockName, uint blockBinding)
|
||||
{
|
||||
var index = (uint) GL.GetUniformBlockIndex(_handle, blockName);
|
||||
var index = (uint) GL.GetUniformBlockIndex(Handle, blockName);
|
||||
_clyde.CheckGlError();
|
||||
GL.UniformBlockBinding(_handle, index, blockBinding);
|
||||
GL.UniformBlockBinding(Handle, index, blockBinding);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private class GLUniformBuffer<T> where T : unmanaged, IAppliableUniformSet
|
||||
{
|
||||
private readonly Clyde _clyde;
|
||||
private readonly int _index;
|
||||
|
||||
/// <summary>
|
||||
/// GPU Buffer (only used when uniform buffers are available)
|
||||
@@ -37,13 +38,23 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public GLUniformBuffer(Clyde clyde, int index, string? name = null)
|
||||
{
|
||||
_clyde = clyde;
|
||||
_index = index;
|
||||
if (_clyde._hasGLUniformBuffers)
|
||||
{
|
||||
_implUBO = new GLBuffer(_clyde, BufferTarget.UniformBuffer, BufferUsageHint.StreamDraw, name);
|
||||
unsafe {
|
||||
_implUBO.Reallocate(sizeof(T));
|
||||
}
|
||||
GL.BindBufferBase(BufferRangeTarget.UniformBuffer, index, (int) _implUBO.ObjectHandle);
|
||||
|
||||
Rebind();
|
||||
}
|
||||
}
|
||||
|
||||
public void Rebind()
|
||||
{
|
||||
if (_implUBO != null)
|
||||
{
|
||||
GL.BindBufferBase(BufferRangeTarget.UniformBuffer, _index, (int) _implUBO!.ObjectHandle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
Robust.Client/Graphics/Clyde/Shaders/winblit.frag
Normal file
16
Robust.Client/Graphics/Clyde/Shaders/winblit.frag
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef HAS_VARYING_ATTRIBUTE
|
||||
#define texture2D texture
|
||||
#define varying in
|
||||
#define attribute in
|
||||
#define gl_FragColor colourOutput
|
||||
out highp vec4 colourOutput;
|
||||
#endif
|
||||
|
||||
varying highp vec2 UV;
|
||||
|
||||
uniform sampler2D tex;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = texture2D(tex, UV);
|
||||
}
|
||||
20
Robust.Client/Graphics/Clyde/Shaders/winblit.vert
Normal file
20
Robust.Client/Graphics/Clyde/Shaders/winblit.vert
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef HAS_VARYING_ATTRIBUTE
|
||||
#define texture2D texture
|
||||
#define varying out
|
||||
#define attribute in
|
||||
#endif
|
||||
|
||||
|
||||
// Vertex position.
|
||||
/*layout (location = 0)*/ attribute vec2 aPos;
|
||||
// Texture coordinates.
|
||||
/*layout (location = 1)*/ attribute vec2 tCoord;
|
||||
|
||||
varying vec2 UV;
|
||||
|
||||
void main()
|
||||
{
|
||||
UV = tCoord;
|
||||
|
||||
gl_Position = vec4(aPos, 0.0, 1.0);
|
||||
}
|
||||
173
Robust.Client/Graphics/Clyde/Windowing/Glfw.Cursors.cs
Normal file
173
Robust.Client/Graphics/Clyde/Windowing/Glfw.Cursors.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using OpenToolkit.GraphicsLibraryFramework;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using GlfwImage = OpenToolkit.GraphicsLibraryFramework.Image;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
private sealed unsafe partial class GlfwWindowingImpl
|
||||
{
|
||||
private readonly Dictionary<ClydeHandle, WinThreadCursorReg> _winThreadCursors = new();
|
||||
private readonly Dictionary<StandardCursorShape, CursorImpl> _standardCursors = new();
|
||||
|
||||
public ICursor CursorGetStandard(StandardCursorShape shape)
|
||||
{
|
||||
return _standardCursors[shape];
|
||||
}
|
||||
|
||||
public ICursor CursorCreate(Image<Rgba32> image, Vector2i hotSpot)
|
||||
{
|
||||
var cloneImg = new Image<Rgba32>(image.Width, image.Height);
|
||||
image.GetPixelSpan().CopyTo(cloneImg.GetPixelSpan());
|
||||
|
||||
var id = _clyde.AllocRid();
|
||||
SendCmd(new CmdCursorCreate(cloneImg, hotSpot, id));
|
||||
|
||||
return new CursorImpl(this, id, false);
|
||||
}
|
||||
|
||||
private void WinThreadCursorCreate(CmdCursorCreate cmd)
|
||||
{
|
||||
var (img, (hotX, hotY), id) = cmd;
|
||||
|
||||
fixed (Rgba32* pixPtr = img.GetPixelSpan())
|
||||
{
|
||||
var gImg = new GlfwImage(img.Width, img.Height, (byte*) pixPtr);
|
||||
var ptr = GLFW.CreateCursor(gImg, hotX, hotY);
|
||||
|
||||
_winThreadCursors.Add(id, new WinThreadCursorReg {Ptr = ptr});
|
||||
}
|
||||
|
||||
img.Dispose();
|
||||
}
|
||||
|
||||
public void CursorSet(WindowReg window, ICursor? cursor)
|
||||
{
|
||||
CheckWindowDisposed(window);
|
||||
|
||||
var reg = (GlfwWindowReg) window;
|
||||
|
||||
if (reg.Cursor == cursor)
|
||||
{
|
||||
// Nothing has to be done!
|
||||
return;
|
||||
}
|
||||
|
||||
if (cursor == null)
|
||||
{
|
||||
reg.Cursor = null;
|
||||
SendCmd(new CmdWinCursorSet((nint) reg.GlfwWindow, default));
|
||||
return;
|
||||
}
|
||||
|
||||
var impl = (CursorImpl) cursor;
|
||||
DebugTools.Assert(impl.Owner == this);
|
||||
|
||||
if (impl.Id == default)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(cursor));
|
||||
}
|
||||
|
||||
reg.Cursor = impl;
|
||||
SendCmd(new CmdWinCursorSet((nint) reg.GlfwWindow, impl.Id));
|
||||
}
|
||||
|
||||
private void WinThreadWinCursorSet(CmdWinCursorSet cmd)
|
||||
{
|
||||
var window = (Window*) cmd.Window;
|
||||
Cursor* ptr = null;
|
||||
if (cmd.Cursor != default)
|
||||
ptr = _winThreadCursors[cmd.Cursor].Ptr;
|
||||
|
||||
if (_win32Experience)
|
||||
{
|
||||
// Based on a true story.
|
||||
Thread.Sleep(15);
|
||||
}
|
||||
|
||||
GLFW.SetCursor(window, ptr);
|
||||
}
|
||||
|
||||
private void WinThreadCursorDestroy(CmdCursorDestroy cmd)
|
||||
{
|
||||
var cursorReg = _winThreadCursors[cmd.Cursor];
|
||||
|
||||
GLFW.DestroyCursor(cursorReg.Ptr);
|
||||
|
||||
_winThreadCursors.Remove(cmd.Cursor);
|
||||
}
|
||||
|
||||
private void InitCursors()
|
||||
{
|
||||
// Gets ran on window thread don't worry about it.
|
||||
|
||||
void AddStandardCursor(StandardCursorShape standardShape, CursorShape shape)
|
||||
{
|
||||
var id = _clyde.AllocRid();
|
||||
var ptr = GLFW.CreateStandardCursor(shape);
|
||||
|
||||
var impl = new CursorImpl(this, id, true);
|
||||
|
||||
_standardCursors.Add(standardShape, impl);
|
||||
_winThreadCursors.Add(id, new WinThreadCursorReg {Ptr = ptr});
|
||||
}
|
||||
|
||||
AddStandardCursor(StandardCursorShape.Arrow, CursorShape.Arrow);
|
||||
AddStandardCursor(StandardCursorShape.IBeam, CursorShape.IBeam);
|
||||
AddStandardCursor(StandardCursorShape.Crosshair, CursorShape.Crosshair);
|
||||
AddStandardCursor(StandardCursorShape.Hand, CursorShape.Hand);
|
||||
AddStandardCursor(StandardCursorShape.HResize, CursorShape.HResize);
|
||||
AddStandardCursor(StandardCursorShape.VResize, CursorShape.VResize);
|
||||
}
|
||||
|
||||
private sealed class CursorImpl : ICursor
|
||||
{
|
||||
private readonly bool _standard;
|
||||
public GlfwWindowingImpl Owner { get; }
|
||||
public ClydeHandle Id { get; private set; }
|
||||
|
||||
public CursorImpl(GlfwWindowingImpl clyde, ClydeHandle id, bool standard)
|
||||
{
|
||||
_standard = standard;
|
||||
Owner = clyde;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
~CursorImpl()
|
||||
{
|
||||
DisposeImpl();
|
||||
}
|
||||
|
||||
private void DisposeImpl()
|
||||
{
|
||||
Owner.SendCmd(new CmdCursorDestroy(Id));
|
||||
Id = default;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_standard)
|
||||
{
|
||||
throw new InvalidOperationException("Can't dispose standard cursor shape.");
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
DisposeImpl();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WinThreadCursorReg
|
||||
{
|
||||
public Cursor* Ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
256
Robust.Client/Graphics/Clyde/Windowing/Glfw.Events.cs
Normal file
256
Robust.Client/Graphics/Clyde/Windowing/Glfw.Events.cs
Normal file
@@ -0,0 +1,256 @@
|
||||
using System;
|
||||
using OpenToolkit.GraphicsLibraryFramework;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
partial class Clyde
|
||||
{
|
||||
private partial class GlfwWindowingImpl
|
||||
{
|
||||
public void ProcessEvents(bool single=false)
|
||||
{
|
||||
while (_eventReader.TryRead(out var ev))
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessEvent(ev);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error(
|
||||
"clyde.win",
|
||||
$"Caught exception in windowing event ({ev.GetType()}):\n{e}");
|
||||
}
|
||||
|
||||
if (single)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Block waiting on the windowing -> game thread channel.
|
||||
// I swear to god do not use this unless you know what you are doing.
|
||||
private void WaitEvents()
|
||||
{
|
||||
_eventReader.WaitToReadAsync().AsTask().Wait();
|
||||
}
|
||||
|
||||
private void ProcessEvent(EventBase evb)
|
||||
{
|
||||
switch (evb)
|
||||
{
|
||||
case EventMouseButton mb:
|
||||
ProcessEventMouseButton(mb);
|
||||
break;
|
||||
case EventCursorPos cp:
|
||||
ProcessEventCursorPos(cp);
|
||||
break;
|
||||
case EventCursorEnter ev:
|
||||
ProcessEventCursorEnter(ev);
|
||||
break;
|
||||
case EventScroll s:
|
||||
ProcessEventScroll(s);
|
||||
break;
|
||||
case EventKey k:
|
||||
ProcessEventKey(k);
|
||||
break;
|
||||
case EventChar c:
|
||||
ProcessEventChar(c);
|
||||
break;
|
||||
case EventMonitorSetup ms:
|
||||
ProcessSetupMonitor(ms);
|
||||
break;
|
||||
case EventMonitorDestroy md:
|
||||
ProcessEventDestroyMonitor(md);
|
||||
break;
|
||||
case EventWindowCreate wCreate:
|
||||
FinishWindowCreate(wCreate);
|
||||
break;
|
||||
case EventWindowClose wc:
|
||||
ProcessEventWindowClose(wc);
|
||||
break;
|
||||
case EventWindowFocus wf:
|
||||
ProcessEventWindowFocus(wf);
|
||||
break;
|
||||
case EventWindowSize ws:
|
||||
ProcessEventWindowSize(ws);
|
||||
break;
|
||||
case EventWindowPos wp:
|
||||
ProcessEventWindowPos(wp);
|
||||
break;
|
||||
case EventWindowIconify wi:
|
||||
ProcessEventWindowIconify(wi);
|
||||
break;
|
||||
case EventWindowContentScale cs:
|
||||
ProcessEventWindowContentScale(cs);
|
||||
break;
|
||||
default:
|
||||
_sawmill.Error($"Unknown GLFW event type: {evb.GetType()}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessEventChar(EventChar ev)
|
||||
{
|
||||
_clyde.SendText(new TextEventArgs(ev.CodePoint));
|
||||
}
|
||||
|
||||
private void ProcessEventCursorPos(EventCursorPos ev)
|
||||
{
|
||||
var windowReg = FindWindow(ev.Window);
|
||||
if (windowReg == null)
|
||||
return;
|
||||
|
||||
var newPos = ((float) ev.XPos, (float) ev.YPos) * windowReg.PixelRatio;
|
||||
var delta = newPos - windowReg.LastMousePos;
|
||||
windowReg.LastMousePos = newPos;
|
||||
|
||||
_clyde._currentHoveredWindow = windowReg;
|
||||
|
||||
_clyde.SendMouseMove(new MouseMoveEventArgs(delta, new ScreenCoordinates(newPos, windowReg.Id)));
|
||||
}
|
||||
|
||||
private void ProcessEventCursorEnter(EventCursorEnter ev)
|
||||
{
|
||||
var windowReg = FindWindow(ev.Window);
|
||||
if (windowReg == null)
|
||||
return;
|
||||
|
||||
if (ev.Entered)
|
||||
{
|
||||
_clyde._currentHoveredWindow = windowReg;
|
||||
}
|
||||
else if (_clyde._currentHoveredWindow == windowReg)
|
||||
{
|
||||
_clyde._currentHoveredWindow = null;
|
||||
}
|
||||
|
||||
_clyde.SendMouseEnterLeave(new MouseEnterLeaveEventArgs(windowReg.Handle, ev.Entered));
|
||||
}
|
||||
|
||||
private void ProcessEventKey(EventKey ev)
|
||||
{
|
||||
EmitKeyEvent(ConvertGlfwKey(ev.Key), ev.Action, ev.Mods);
|
||||
}
|
||||
|
||||
private void EmitKeyEvent(Keyboard.Key key, InputAction action, KeyModifiers mods)
|
||||
{
|
||||
var shift = (mods & KeyModifiers.Shift) != 0;
|
||||
var alt = (mods & KeyModifiers.Alt) != 0;
|
||||
var control = (mods & KeyModifiers.Control) != 0;
|
||||
var system = (mods & KeyModifiers.Super) != 0;
|
||||
|
||||
var ev = new KeyEventArgs(
|
||||
key,
|
||||
action == InputAction.Repeat,
|
||||
alt, control, shift, system);
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case InputAction.Release:
|
||||
_clyde.SendKeyUp(ev);
|
||||
break;
|
||||
case InputAction.Press:
|
||||
case InputAction.Repeat:
|
||||
_clyde.SendKeyDown(ev);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(action), action, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessEventMouseButton(EventMouseButton ev)
|
||||
{
|
||||
EmitKeyEvent(Mouse.MouseButtonToKey(ConvertGlfwButton(ev.Button)), ev.Action, ev.Mods);
|
||||
}
|
||||
|
||||
private void ProcessEventScroll(EventScroll ev)
|
||||
{
|
||||
var windowReg = FindWindow(ev.Window);
|
||||
if (windowReg == null)
|
||||
return;
|
||||
|
||||
var eventArgs = new MouseWheelEventArgs(
|
||||
((float) ev.XOffset, (float) ev.YOffset),
|
||||
new ScreenCoordinates(windowReg.LastMousePos, windowReg.Id));
|
||||
_clyde.SendScroll(eventArgs);
|
||||
}
|
||||
|
||||
private void ProcessEventWindowClose(EventWindowClose ev)
|
||||
{
|
||||
var windowReg = FindWindow(ev.Window);
|
||||
if (windowReg == null)
|
||||
return;
|
||||
|
||||
_clyde.SendCloseWindow(windowReg, new WindowClosedEventArgs(windowReg.Handle));
|
||||
}
|
||||
|
||||
private void ProcessEventWindowSize(EventWindowSize ev)
|
||||
{
|
||||
var window = ev.Window;
|
||||
var width = ev.Width;
|
||||
var height = ev.Height;
|
||||
var fbW = ev.FramebufferWidth;
|
||||
var fbH = ev.FramebufferHeight;
|
||||
|
||||
var windowReg = FindWindow(window);
|
||||
if (windowReg == null)
|
||||
return;
|
||||
|
||||
var oldSize = windowReg.FramebufferSize;
|
||||
windowReg.FramebufferSize = (fbW, fbH);
|
||||
windowReg.WindowSize = (width, height);
|
||||
|
||||
if (fbW == 0 || fbH == 0 || width == 0 || height == 0)
|
||||
return;
|
||||
|
||||
windowReg.PixelRatio = windowReg.FramebufferSize / windowReg.WindowSize;
|
||||
|
||||
_clyde.SendWindowResized(windowReg, oldSize);
|
||||
}
|
||||
|
||||
private void ProcessEventWindowPos(EventWindowPos ev)
|
||||
{
|
||||
var window = ev.Window;
|
||||
var x = ev.X;
|
||||
var y = ev.Y;
|
||||
|
||||
var windowReg = FindWindow(window);
|
||||
if (windowReg == null)
|
||||
return;
|
||||
|
||||
windowReg.WindowPos = (x, y);
|
||||
}
|
||||
|
||||
private void ProcessEventWindowContentScale(EventWindowContentScale ev)
|
||||
{
|
||||
var windowReg = FindWindow(ev.Window);
|
||||
if (windowReg == null)
|
||||
return;
|
||||
|
||||
windowReg.WindowScale = (ev.XScale, ev.YScale);
|
||||
_clyde.SendWindowContentScaleChanged(new WindowContentScaleEventArgs(windowReg.Handle));
|
||||
}
|
||||
|
||||
private void ProcessEventWindowIconify(EventWindowIconify ev)
|
||||
{
|
||||
var windowReg = FindWindow(ev.Window);
|
||||
if (windowReg == null)
|
||||
return;
|
||||
|
||||
windowReg.IsMinimized = ev.Iconified;
|
||||
}
|
||||
|
||||
private void ProcessEventWindowFocus(EventWindowFocus ev)
|
||||
{
|
||||
var windowReg = FindWindow(ev.Window);
|
||||
if (windowReg == null)
|
||||
return;
|
||||
|
||||
windowReg.IsFocused = ev.Focused;
|
||||
_clyde.SendWindowFocus(new WindowFocusedEventArgs(ev.Focused, windowReg.Handle));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
225
Robust.Client/Graphics/Clyde/Windowing/Glfw.Keys.cs
Normal file
225
Robust.Client/Graphics/Clyde/Windowing/Glfw.Keys.cs
Normal file
@@ -0,0 +1,225 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using OpenToolkit.GraphicsLibraryFramework;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.Localization;
|
||||
using GlfwKey = OpenToolkit.GraphicsLibraryFramework.Keys;
|
||||
using GlfwButton = OpenToolkit.GraphicsLibraryFramework.MouseButton;
|
||||
using static Robust.Client.Input.Mouse;
|
||||
using static Robust.Client.Input.Keyboard;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
private sealed partial class GlfwWindowingImpl
|
||||
{
|
||||
// TODO: to avoid having to ask the windowing thread, key names are cached.
|
||||
// This means they don't update correctly if the user switches keyboard mode. RIP.
|
||||
|
||||
private readonly Dictionary<Key, string> _printableKeyNameMap = new();
|
||||
|
||||
private void InitKeyMap()
|
||||
{
|
||||
// From GLFW's source code: this is the actual list of "printable" keys
|
||||
// that GetKeyName returns something for.
|
||||
CacheKey(Keys.KeyPadEqual);
|
||||
for (var k = Keys.KeyPad0; k <= Keys.KeyPadAdd; k++)
|
||||
{
|
||||
CacheKey(k);
|
||||
}
|
||||
|
||||
for (var k = Keys.Apostrophe; k <= Keys.World2; k++)
|
||||
{
|
||||
CacheKey(k);
|
||||
}
|
||||
|
||||
void CacheKey(GlfwKey key)
|
||||
{
|
||||
var rKey = ConvertGlfwKey(key);
|
||||
if (rKey == Key.Unknown)
|
||||
return;
|
||||
|
||||
var name = GLFW.GetKeyName(key, 0);
|
||||
_printableKeyNameMap.Add(rKey, name);
|
||||
}
|
||||
}
|
||||
|
||||
public string KeyGetName(Keyboard.Key key)
|
||||
{
|
||||
if (_printableKeyNameMap.TryGetValue(key, out var name))
|
||||
{
|
||||
var textInfo = Thread.CurrentThread.CurrentCulture.TextInfo;
|
||||
return textInfo.ToTitleCase(name);
|
||||
}
|
||||
|
||||
name = Keyboard.GetSpecialKeyName(key, _loc);
|
||||
if (name != null)
|
||||
{
|
||||
return Loc.GetString(name);
|
||||
}
|
||||
|
||||
return Loc.GetString("<unknown key>");
|
||||
}
|
||||
|
||||
public static Button ConvertGlfwButton(GlfwButton button)
|
||||
{
|
||||
return MouseButtonMap[button];
|
||||
}
|
||||
|
||||
private static readonly Dictionary<GlfwButton, Button> MouseButtonMap = new()
|
||||
{
|
||||
{GlfwButton.Left, Button.Left},
|
||||
{GlfwButton.Middle, Button.Middle},
|
||||
{GlfwButton.Right, Button.Right},
|
||||
{GlfwButton.Button4, Button.Button4},
|
||||
{GlfwButton.Button5, Button.Button5},
|
||||
{GlfwButton.Button6, Button.Button6},
|
||||
{GlfwButton.Button7, Button.Button7},
|
||||
{GlfwButton.Button8, Button.Button8},
|
||||
};
|
||||
|
||||
private static readonly Dictionary<GlfwKey, Key> KeyMap;
|
||||
private static readonly Dictionary<Key, GlfwKey> KeyMapReverse;
|
||||
|
||||
|
||||
internal static Key ConvertGlfwKey(GlfwKey key)
|
||||
{
|
||||
if (KeyMap.TryGetValue(key, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return Key.Unknown;
|
||||
}
|
||||
|
||||
internal static GlfwKey ConvertGlfwKeyReverse(Key key)
|
||||
{
|
||||
if (KeyMapReverse.TryGetValue(key, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return GlfwKey.Unknown;
|
||||
}
|
||||
|
||||
static GlfwWindowingImpl()
|
||||
{
|
||||
KeyMap = new Dictionary<GlfwKey, Key>
|
||||
{
|
||||
{GlfwKey.A, Key.A},
|
||||
{GlfwKey.B, Key.B},
|
||||
{GlfwKey.C, Key.C},
|
||||
{GlfwKey.D, Key.D},
|
||||
{GlfwKey.E, Key.E},
|
||||
{GlfwKey.F, Key.F},
|
||||
{GlfwKey.G, Key.G},
|
||||
{GlfwKey.H, Key.H},
|
||||
{GlfwKey.I, Key.I},
|
||||
{GlfwKey.J, Key.J},
|
||||
{GlfwKey.K, Key.K},
|
||||
{GlfwKey.L, Key.L},
|
||||
{GlfwKey.M, Key.M},
|
||||
{GlfwKey.N, Key.N},
|
||||
{GlfwKey.O, Key.O},
|
||||
{GlfwKey.P, Key.P},
|
||||
{GlfwKey.Q, Key.Q},
|
||||
{GlfwKey.R, Key.R},
|
||||
{GlfwKey.S, Key.S},
|
||||
{GlfwKey.T, Key.T},
|
||||
{GlfwKey.U, Key.U},
|
||||
{GlfwKey.V, Key.V},
|
||||
{GlfwKey.W, Key.W},
|
||||
{GlfwKey.X, Key.X},
|
||||
{GlfwKey.Y, Key.Y},
|
||||
{GlfwKey.Z, Key.Z},
|
||||
{GlfwKey.D0, Key.Num0},
|
||||
{GlfwKey.D1, Key.Num1},
|
||||
{GlfwKey.D2, Key.Num2},
|
||||
{GlfwKey.D3, Key.Num3},
|
||||
{GlfwKey.D4, Key.Num4},
|
||||
{GlfwKey.D5, Key.Num5},
|
||||
{GlfwKey.D6, Key.Num6},
|
||||
{GlfwKey.D7, Key.Num7},
|
||||
{GlfwKey.D8, Key.Num8},
|
||||
{GlfwKey.D9, Key.Num9},
|
||||
{GlfwKey.KeyPad0, Key.NumpadNum0},
|
||||
{GlfwKey.KeyPad1, Key.NumpadNum1},
|
||||
{GlfwKey.KeyPad2, Key.NumpadNum2},
|
||||
{GlfwKey.KeyPad3, Key.NumpadNum3},
|
||||
{GlfwKey.KeyPad4, Key.NumpadNum4},
|
||||
{GlfwKey.KeyPad5, Key.NumpadNum5},
|
||||
{GlfwKey.KeyPad6, Key.NumpadNum6},
|
||||
{GlfwKey.KeyPad7, Key.NumpadNum7},
|
||||
{GlfwKey.KeyPad8, Key.NumpadNum8},
|
||||
{GlfwKey.KeyPad9, Key.NumpadNum9},
|
||||
{GlfwKey.Escape, Key.Escape},
|
||||
{GlfwKey.LeftControl, Key.Control},
|
||||
{GlfwKey.RightControl, Key.Control},
|
||||
{GlfwKey.RightShift, Key.Shift},
|
||||
{GlfwKey.LeftShift, Key.Shift},
|
||||
{GlfwKey.LeftAlt, Key.Alt},
|
||||
{GlfwKey.RightAlt, Key.Alt},
|
||||
{GlfwKey.LeftSuper, Key.LSystem},
|
||||
{GlfwKey.RightSuper, Key.RSystem},
|
||||
{GlfwKey.Menu, Key.Menu},
|
||||
{GlfwKey.LeftBracket, Key.LBracket},
|
||||
{GlfwKey.RightBracket, Key.RBracket},
|
||||
{GlfwKey.Semicolon, Key.SemiColon},
|
||||
{GlfwKey.Comma, Key.Comma},
|
||||
{GlfwKey.Period, Key.Period},
|
||||
{GlfwKey.Apostrophe, Key.Apostrophe},
|
||||
{GlfwKey.Slash, Key.Slash},
|
||||
{GlfwKey.Backslash, Key.BackSlash},
|
||||
{GlfwKey.GraveAccent, Key.Tilde},
|
||||
{GlfwKey.Equal, Key.Equal},
|
||||
{GlfwKey.Space, Key.Space},
|
||||
{GlfwKey.Enter, Key.Return},
|
||||
{GlfwKey.KeyPadEnter, Key.NumpadEnter},
|
||||
{GlfwKey.Backspace, Key.BackSpace},
|
||||
{GlfwKey.Tab, Key.Tab},
|
||||
{GlfwKey.PageUp, Key.PageUp},
|
||||
{GlfwKey.PageDown, Key.PageDown},
|
||||
{GlfwKey.End, Key.End},
|
||||
{GlfwKey.Home, Key.Home},
|
||||
{GlfwKey.Insert, Key.Insert},
|
||||
{GlfwKey.Delete, Key.Delete},
|
||||
{GlfwKey.Minus, Key.Minus},
|
||||
{GlfwKey.KeyPadAdd, Key.NumpadAdd},
|
||||
{GlfwKey.KeyPadSubtract, Key.NumpadSubtract},
|
||||
{GlfwKey.KeyPadDivide, Key.NumpadDivide},
|
||||
{GlfwKey.KeyPadMultiply, Key.NumpadMultiply},
|
||||
{GlfwKey.KeyPadDecimal, Key.NumpadDecimal},
|
||||
{GlfwKey.Left, Key.Left},
|
||||
{GlfwKey.Right, Key.Right},
|
||||
{GlfwKey.Up, Key.Up},
|
||||
{GlfwKey.Down, Key.Down},
|
||||
{GlfwKey.F1, Key.F1},
|
||||
{GlfwKey.F2, Key.F2},
|
||||
{GlfwKey.F3, Key.F3},
|
||||
{GlfwKey.F4, Key.F4},
|
||||
{GlfwKey.F5, Key.F5},
|
||||
{GlfwKey.F6, Key.F6},
|
||||
{GlfwKey.F7, Key.F7},
|
||||
{GlfwKey.F8, Key.F8},
|
||||
{GlfwKey.F9, Key.F9},
|
||||
{GlfwKey.F10, Key.F10},
|
||||
{GlfwKey.F11, Key.F11},
|
||||
{GlfwKey.F12, Key.F12},
|
||||
{GlfwKey.F13, Key.F13},
|
||||
{GlfwKey.F14, Key.F14},
|
||||
{GlfwKey.F15, Key.F15},
|
||||
{GlfwKey.Pause, Key.Pause},
|
||||
};
|
||||
|
||||
KeyMapReverse = new Dictionary<Key, GlfwKey>();
|
||||
|
||||
foreach (var (key, value) in KeyMap)
|
||||
{
|
||||
KeyMapReverse[value] = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
133
Robust.Client/Graphics/Clyde/Windowing/Glfw.Monitors.cs
Normal file
133
Robust.Client/Graphics/Clyde/Windowing/Glfw.Monitors.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using OpenToolkit.GraphicsLibraryFramework;
|
||||
using Robust.Shared.Utility;
|
||||
using GlfwVideoMode = OpenToolkit.GraphicsLibraryFramework.VideoMode;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
private sealed unsafe partial class GlfwWindowingImpl
|
||||
{
|
||||
// TODO: GLFW doesn't have any events for complex monitor config changes,
|
||||
// so we need some way to reload stuff if e.g. the primary monitor changes.
|
||||
// Still better than SDL2 though which doesn't acknowledge monitor changes at all.
|
||||
|
||||
// Monitors are created at GLFW's will,
|
||||
// so we need to make SURE monitors keep existing while operating on them.
|
||||
// because, you know, async. Don't want a use-after-free.
|
||||
private readonly Dictionary<int, WinThreadMonitorReg> _winThreadMonitors = new();
|
||||
|
||||
// Can't use ClydeHandle because it's 64 bit.
|
||||
private int _nextMonitorId = 1;
|
||||
private int _primaryMonitorId;
|
||||
private readonly Dictionary<int, GlfwMonitorReg> _monitors = new();
|
||||
|
||||
private void InitMonitors()
|
||||
{
|
||||
var monitors = GLFW.GetMonitorsRaw(out var count);
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
WinThreadSetupMonitor(monitors[i]);
|
||||
}
|
||||
|
||||
var primaryMonitor = GLFW.GetPrimaryMonitor();
|
||||
var up = GLFW.GetMonitorUserPointer(primaryMonitor);
|
||||
_primaryMonitorId = (int) up;
|
||||
|
||||
ProcessEvents();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void WinThreadSetupMonitor(Monitor* monitor)
|
||||
{
|
||||
var id = _nextMonitorId++;
|
||||
|
||||
DebugTools.Assert(GLFW.GetMonitorUserPointer(monitor) == null,
|
||||
"GLFW window already has user pointer??");
|
||||
|
||||
var name = GLFW.GetMonitorName(monitor);
|
||||
var videoMode = GLFW.GetVideoMode(monitor);
|
||||
var modesPtr = GLFW.GetVideoModesRaw(monitor, out var modeCount);
|
||||
var modes = new VideoMode[modeCount];
|
||||
for (var i = 0; i < modes.Length; i++)
|
||||
{
|
||||
modes[i] = ConvertVideoMode(modesPtr[i]);
|
||||
}
|
||||
|
||||
GLFW.SetMonitorUserPointer(monitor, (void*) id);
|
||||
|
||||
_winThreadMonitors.Add(id, new WinThreadMonitorReg {Ptr = monitor});
|
||||
|
||||
SendEvent(new EventMonitorSetup(id, name, ConvertVideoMode(*videoMode), modes));
|
||||
}
|
||||
|
||||
private static VideoMode ConvertVideoMode(in GlfwVideoMode mode)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Width = (ushort) mode.Width,
|
||||
Height = (ushort) mode.Height,
|
||||
RedBits = (byte) mode.RedBits,
|
||||
RefreshRate = (ushort) mode.RefreshRate,
|
||||
GreenBits = (byte) mode.GreenBits,
|
||||
BlueBits = (byte) mode.BlueBits,
|
||||
};
|
||||
}
|
||||
|
||||
private void ProcessSetupMonitor(EventMonitorSetup ev)
|
||||
{
|
||||
var impl = new MonitorHandle(
|
||||
ev.Id,
|
||||
ev.Name,
|
||||
(ev.CurrentMode.Width, ev.CurrentMode.Height),
|
||||
ev.CurrentMode.RefreshRate,
|
||||
ev.AllModes);
|
||||
|
||||
_clyde._monitorHandles.Add(impl);
|
||||
_monitors[ev.Id] = new GlfwMonitorReg
|
||||
{
|
||||
Id = ev.Id,
|
||||
Handle = impl
|
||||
};
|
||||
}
|
||||
|
||||
private void WinThreadDestroyMonitor(Monitor* monitor)
|
||||
{
|
||||
var ptr = (int) GLFW.GetMonitorUserPointer(monitor);
|
||||
|
||||
if (ptr == 0)
|
||||
{
|
||||
var name = GLFW.GetMonitorName(monitor);
|
||||
_sawmill.Warning($"Monitor '{name}' had no user pointer set??");
|
||||
return;
|
||||
}
|
||||
|
||||
_winThreadMonitors.Remove(ptr);
|
||||
|
||||
GLFW.SetMonitorUserPointer(monitor, null);
|
||||
|
||||
SendEvent(new EventMonitorDestroy(ptr));
|
||||
}
|
||||
|
||||
private void ProcessEventDestroyMonitor(EventMonitorDestroy ev)
|
||||
{
|
||||
var reg = _monitors[ev.Id];
|
||||
_monitors.Remove(ev.Id);
|
||||
_clyde._monitorHandles.Remove(reg.Handle);
|
||||
}
|
||||
|
||||
private sealed class GlfwMonitorReg : MonitorReg
|
||||
{
|
||||
public int Id;
|
||||
}
|
||||
|
||||
private sealed class WinThreadMonitorReg
|
||||
{
|
||||
public Monitor* Ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
223
Robust.Client/Graphics/Clyde/Windowing/Glfw.RawEvents.cs
Normal file
223
Robust.Client/Graphics/Clyde/Windowing/Glfw.RawEvents.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
using System.Threading.Tasks;
|
||||
using OpenToolkit.GraphicsLibraryFramework;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
partial class Clyde
|
||||
{
|
||||
private unsafe partial class GlfwWindowingImpl
|
||||
{
|
||||
// Keep delegates around to prevent GC issues.
|
||||
private GLFWCallbacks.ErrorCallback? _errorCallback;
|
||||
private GLFWCallbacks.MonitorCallback? _monitorCallback;
|
||||
private GLFWCallbacks.CharCallback? _charCallback;
|
||||
private GLFWCallbacks.CursorPosCallback? _cursorPosCallback;
|
||||
private GLFWCallbacks.CursorEnterCallback? _cursorEnterCallback;
|
||||
private GLFWCallbacks.KeyCallback? _keyCallback;
|
||||
private GLFWCallbacks.MouseButtonCallback? _mouseButtonCallback;
|
||||
private GLFWCallbacks.ScrollCallback? _scrollCallback;
|
||||
private GLFWCallbacks.WindowCloseCallback? _windowCloseCallback;
|
||||
private GLFWCallbacks.WindowPosCallback? _windowPosCallback;
|
||||
private GLFWCallbacks.WindowSizeCallback? _windowSizeCallback;
|
||||
private GLFWCallbacks.WindowContentScaleCallback? _windowContentScaleCallback;
|
||||
private GLFWCallbacks.WindowIconifyCallback? _windowIconifyCallback;
|
||||
private GLFWCallbacks.WindowFocusCallback? _windowFocusCallback;
|
||||
|
||||
private void StoreCallbacks()
|
||||
{
|
||||
_errorCallback = OnGlfwError;
|
||||
_monitorCallback = OnGlfwMonitor;
|
||||
_charCallback = OnGlfwChar;
|
||||
_cursorPosCallback = OnGlfwCursorPos;
|
||||
_cursorEnterCallback = OnGlfwCursorEnter;
|
||||
_keyCallback = OnGlfwKey;
|
||||
_mouseButtonCallback = OnGlfwMouseButton;
|
||||
_scrollCallback = OnGlfwScroll;
|
||||
_windowCloseCallback = OnGlfwWindowClose;
|
||||
_windowSizeCallback = OnGlfwWindowSize;
|
||||
_windowPosCallback = OnGlfwWindowPos;
|
||||
_windowContentScaleCallback = OnGlfwWindowContentScale;
|
||||
_windowIconifyCallback = OnGlfwWindowIconify;
|
||||
_windowFocusCallback = OnGlfwWindowFocus;
|
||||
}
|
||||
|
||||
private void SetupGlobalCallbacks()
|
||||
{
|
||||
GLFW.SetMonitorCallback(_monitorCallback);
|
||||
}
|
||||
|
||||
private void OnGlfwMonitor(Monitor* monitor, ConnectedState state)
|
||||
{
|
||||
if (state == ConnectedState.Connected)
|
||||
WinThreadSetupMonitor(monitor);
|
||||
else
|
||||
WinThreadDestroyMonitor(monitor);
|
||||
}
|
||||
|
||||
private void OnGlfwChar(Window* window, uint codepoint)
|
||||
{
|
||||
SendEvent(new EventChar((nint) window, codepoint));
|
||||
}
|
||||
|
||||
private void OnGlfwCursorPos(Window* window, double x, double y)
|
||||
{
|
||||
// System.Console.WriteLine($"{(nint)window:X16}: {x},{y}");
|
||||
SendEvent(new EventCursorPos((nint) window, x, y));
|
||||
}
|
||||
|
||||
private void OnGlfwCursorEnter(Window* window, bool entered)
|
||||
{
|
||||
// System.Console.WriteLine($"{(nint)window:X16}: {entered}");
|
||||
SendEvent(new EventCursorEnter((nint) window, entered));
|
||||
}
|
||||
|
||||
private void OnGlfwKey(Window* window, Keys key, int scanCode, InputAction action, KeyModifiers mods)
|
||||
{
|
||||
SendEvent(new EventKey((nint) window, key, scanCode, action, mods));
|
||||
}
|
||||
|
||||
private void OnGlfwMouseButton(Window* window, MouseButton button, InputAction action, KeyModifiers mods)
|
||||
{
|
||||
SendEvent(new EventMouseButton((nint) window, button, action, mods));
|
||||
}
|
||||
|
||||
private void OnGlfwScroll(Window* window, double offsetX, double offsetY)
|
||||
{
|
||||
SendEvent(new EventScroll((nint) window, offsetX, offsetY));
|
||||
}
|
||||
|
||||
private void OnGlfwWindowClose(Window* window)
|
||||
{
|
||||
SendEvent(new EventWindowClose((nint) window));
|
||||
}
|
||||
|
||||
private void OnGlfwWindowSize(Window* window, int width, int height)
|
||||
{
|
||||
GLFW.GetFramebufferSize(window, out var fbW, out var fbH);
|
||||
SendEvent(new EventWindowSize((nint) window, width, height, fbW, fbH));
|
||||
}
|
||||
|
||||
private void OnGlfwWindowPos(Window* window, int x, int y)
|
||||
{
|
||||
SendEvent(new EventWindowPos((nint) window, x, y));
|
||||
}
|
||||
|
||||
private void OnGlfwWindowContentScale(Window* window, float xScale, float yScale)
|
||||
{
|
||||
SendEvent(new EventWindowContentScale((nint) window, xScale, yScale));
|
||||
}
|
||||
|
||||
private void OnGlfwWindowIconify(Window* window, bool iconified)
|
||||
{
|
||||
SendEvent(new EventWindowIconify((nint) window, iconified));
|
||||
}
|
||||
|
||||
private void OnGlfwWindowFocus(Window* window, bool focused)
|
||||
{
|
||||
SendEvent(new EventWindowFocus((nint) window, focused));
|
||||
}
|
||||
|
||||
// NOTE: events do not correspond 1:1 to GLFW events
|
||||
// This is because they need to pack all the data required
|
||||
// for the game-thread event handling.
|
||||
|
||||
private abstract record EventBase;
|
||||
|
||||
private record EventMouseButton(
|
||||
nint Window,
|
||||
MouseButton Button,
|
||||
InputAction Action,
|
||||
KeyModifiers Mods
|
||||
) : EventBase;
|
||||
|
||||
private record EventCursorPos(
|
||||
nint Window,
|
||||
double XPos,
|
||||
double YPos
|
||||
) : EventBase;
|
||||
|
||||
private record EventCursorEnter(
|
||||
nint Window,
|
||||
bool Entered
|
||||
) : EventBase;
|
||||
|
||||
private record EventScroll(
|
||||
nint Window,
|
||||
double XOffset,
|
||||
double YOffset
|
||||
) : EventBase;
|
||||
|
||||
private record EventKey(
|
||||
nint Window,
|
||||
Keys Key,
|
||||
int ScanCode,
|
||||
InputAction Action,
|
||||
KeyModifiers Mods
|
||||
) : EventBase;
|
||||
|
||||
private record EventChar
|
||||
(
|
||||
nint Window,
|
||||
uint CodePoint
|
||||
) : EventBase;
|
||||
|
||||
private record EventWindowClose
|
||||
(
|
||||
nint Window
|
||||
) : EventBase;
|
||||
|
||||
private record EventWindowCreate(
|
||||
GlfwWindowCreateResult Result,
|
||||
TaskCompletionSource<GlfwWindowCreateResult> Tcs
|
||||
) : EventBase;
|
||||
|
||||
private record EventWindowSize
|
||||
(
|
||||
nint Window,
|
||||
int Width,
|
||||
int Height,
|
||||
int FramebufferWidth,
|
||||
int FramebufferHeight
|
||||
) : EventBase;
|
||||
|
||||
private record EventWindowPos
|
||||
(
|
||||
nint Window,
|
||||
int X,
|
||||
int Y
|
||||
) : EventBase;
|
||||
|
||||
private record EventWindowContentScale
|
||||
(
|
||||
nint Window,
|
||||
float XScale,
|
||||
float YScale
|
||||
) : EventBase;
|
||||
|
||||
private record EventWindowIconify
|
||||
(
|
||||
nint Window,
|
||||
bool Iconified
|
||||
) : EventBase;
|
||||
|
||||
private record EventWindowFocus
|
||||
(
|
||||
nint Window,
|
||||
bool Focused
|
||||
) : EventBase;
|
||||
|
||||
private record EventMonitorSetup
|
||||
(
|
||||
int Id,
|
||||
string Name,
|
||||
VideoMode CurrentMode,
|
||||
VideoMode[] AllModes
|
||||
) : EventBase;
|
||||
|
||||
private record EventMonitorDestroy
|
||||
(
|
||||
int Id
|
||||
) : EventBase;
|
||||
}
|
||||
}
|
||||
}
|
||||
250
Robust.Client/Graphics/Clyde/Windowing/Glfw.WindowThread.cs
Normal file
250
Robust.Client/Graphics/Clyde/Windowing/Glfw.WindowThread.cs
Normal file
@@ -0,0 +1,250 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using OpenToolkit.GraphicsLibraryFramework;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
private sealed partial class GlfwWindowingImpl
|
||||
{
|
||||
// glfwPostEmptyEvent is broken on macOS and crashes when not called from the main thread
|
||||
// (despite what the docs claim, and yes this makes it useless).
|
||||
// Because of this, we just forego it and use glfwWaitEventsTimeout on macOS instead.
|
||||
private static readonly bool IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||
|
||||
private bool _windowingRunning;
|
||||
private ChannelWriter<CmdBase> _cmdWriter = default!;
|
||||
private ChannelReader<CmdBase> _cmdReader = default!;
|
||||
|
||||
private ChannelReader<EventBase> _eventReader = default!;
|
||||
private ChannelWriter<EventBase> _eventWriter = default!;
|
||||
|
||||
//
|
||||
// Let it be forever recorded that I started work on windowing thread separation
|
||||
// because win32 SetCursor was taking 15ms spinwaiting inside the kernel.
|
||||
//
|
||||
|
||||
//
|
||||
// To avoid stutters and solve some other problems like smooth window resizing,
|
||||
// we (by default) use a separate thread for windowing.
|
||||
//
|
||||
// Types like WindowReg are considered to be part of the "game" thread
|
||||
// and should **NOT** be directly updated/accessed from the windowing thread.
|
||||
//
|
||||
// Got that?
|
||||
//
|
||||
|
||||
//
|
||||
// The windowing -> game channel is bounded so that the OS properly detects the game as locked
|
||||
// up when it actually locks up. The other way around is not bounded to avoid deadlocks.
|
||||
// This also means that all operations like clipboard reading, window creation, etc....
|
||||
// have to be asynchronous.
|
||||
//
|
||||
|
||||
public void EnterWindowLoop()
|
||||
{
|
||||
_windowingRunning = true;
|
||||
|
||||
while (_windowingRunning)
|
||||
{
|
||||
if (IsMacOS)
|
||||
GLFW.WaitEventsTimeout(0.008);
|
||||
else
|
||||
GLFW.WaitEvents();
|
||||
|
||||
while (_cmdReader.TryRead(out var cmd))
|
||||
{
|
||||
ProcessGlfwCmd(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessGlfwCmd(CmdBase cmdb)
|
||||
{
|
||||
switch (cmdb)
|
||||
{
|
||||
case CmdTerminate:
|
||||
_windowingRunning = false;
|
||||
break;
|
||||
|
||||
case CmdWinSetTitle cmd:
|
||||
WinThreadWinSetTitle(cmd);
|
||||
break;
|
||||
|
||||
case CmdWinSetMonitor cmd:
|
||||
WinThreadWinSetMonitor(cmd);
|
||||
break;
|
||||
|
||||
case CmdWinSetVisible cmd:
|
||||
WinThreadWinSetVisible(cmd);
|
||||
break;
|
||||
|
||||
case CmdWinRequestAttention cmd:
|
||||
WinThreadWinRequestAttention(cmd);
|
||||
break;
|
||||
|
||||
case CmdWinSetFullscreen cmd:
|
||||
WinThreadWinSetFullscreen(cmd);
|
||||
break;
|
||||
|
||||
case CmdWinCreate cmd:
|
||||
WinThreadWinCreate(cmd);
|
||||
break;
|
||||
|
||||
case CmdWinDestroy cmd:
|
||||
WinThreadWinDestroy(cmd);
|
||||
break;
|
||||
|
||||
case CmdSetClipboard cmd:
|
||||
WinThreadSetClipboard(cmd);
|
||||
break;
|
||||
|
||||
case CmdGetClipboard cmd:
|
||||
WinThreadGetClipboard(cmd);
|
||||
break;
|
||||
|
||||
case CmdCursorCreate cmd:
|
||||
WinThreadCursorCreate(cmd);
|
||||
break;
|
||||
|
||||
case CmdCursorDestroy cmd:
|
||||
WinThreadCursorDestroy(cmd);
|
||||
break;
|
||||
|
||||
case CmdWinCursorSet cmd:
|
||||
WinThreadWinCursorSet(cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void TerminateWindowLoop()
|
||||
{
|
||||
SendCmd(new CmdTerminate());
|
||||
}
|
||||
|
||||
private void InitChannels()
|
||||
{
|
||||
var cmdChannel = Channel.CreateUnbounded<CmdBase>(new UnboundedChannelOptions
|
||||
{
|
||||
SingleReader = true,
|
||||
// Finalizers can write to this in some cases.
|
||||
SingleWriter = false
|
||||
});
|
||||
|
||||
_cmdReader = cmdChannel.Reader;
|
||||
_cmdWriter = cmdChannel.Writer;
|
||||
|
||||
var bufferSize = _cfg.GetCVar(CVars.DisplayInputBufferSize);
|
||||
var eventChannel = Channel.CreateBounded<EventBase>(new BoundedChannelOptions(bufferSize)
|
||||
{
|
||||
FullMode = BoundedChannelFullMode.Wait,
|
||||
SingleReader = true,
|
||||
SingleWriter = true
|
||||
});
|
||||
|
||||
_eventReader = eventChannel.Reader;
|
||||
_eventWriter = eventChannel.Writer;
|
||||
}
|
||||
|
||||
private void SendCmd(CmdBase cmd)
|
||||
{
|
||||
_cmdWriter.TryWrite(cmd);
|
||||
|
||||
// Post empty event to unstuck WaitEvents if necessary.
|
||||
if (!IsMacOS)
|
||||
GLFW.PostEmptyEvent();
|
||||
}
|
||||
|
||||
private void SendEvent(EventBase ev)
|
||||
{
|
||||
var task = _eventWriter.WriteAsync(ev);
|
||||
|
||||
if (!task.IsCompletedSuccessfully)
|
||||
{
|
||||
task.AsTask().Wait();
|
||||
}
|
||||
}
|
||||
|
||||
private abstract record CmdBase;
|
||||
|
||||
private sealed record CmdTerminate : CmdBase;
|
||||
|
||||
private sealed record CmdWinSetTitle(
|
||||
nint Window,
|
||||
string Title
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinSetMonitor(
|
||||
nint Window,
|
||||
int MonitorId,
|
||||
int X, int Y,
|
||||
int W, int H,
|
||||
int RefreshRate
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinMaximize(
|
||||
nint Window
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinSetFullscreen(
|
||||
nint Window
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinSetVisible(
|
||||
nint Window,
|
||||
bool Visible
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinRequestAttention(
|
||||
nint Window
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinCreate(
|
||||
Renderer Renderer,
|
||||
WindowCreateParameters Parameters,
|
||||
nint ShareWindow,
|
||||
TaskCompletionSource<GlfwWindowCreateResult> Tcs
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinDestroy(
|
||||
nint Window
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record GlfwWindowCreateResult(
|
||||
GlfwWindowReg? Reg,
|
||||
(string Desc, ErrorCode Code)? Error
|
||||
);
|
||||
|
||||
private sealed record CmdSetClipboard(
|
||||
nint Window,
|
||||
string Text
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdGetClipboard(
|
||||
nint Window,
|
||||
TaskCompletionSource<string> Tcs
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinCursorSet(
|
||||
nint Window,
|
||||
ClydeHandle Cursor
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdCursorCreate(
|
||||
Image<Rgba32> Bytes,
|
||||
Vector2i Hotspot,
|
||||
ClydeHandle Cursor
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdCursorDestroy(
|
||||
ClydeHandle Cursor
|
||||
) : CmdBase;
|
||||
}
|
||||
}
|
||||
}
|
||||
687
Robust.Client/Graphics/Clyde/Windowing/Glfw.Windows.cs
Normal file
687
Robust.Client/Graphics/Clyde/Windowing/Glfw.Windows.cs
Normal file
@@ -0,0 +1,687 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using OpenToolkit;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using OpenToolkit.GraphicsLibraryFramework;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using GlfwImage = OpenToolkit.GraphicsLibraryFramework.Image;
|
||||
using Monitor = OpenToolkit.GraphicsLibraryFramework.Monitor;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
// Wait for it.
|
||||
private sealed partial class GlfwWindowingImpl
|
||||
{
|
||||
private readonly List<GlfwWindowReg> _windows = new();
|
||||
|
||||
public IReadOnlyList<WindowReg> AllWindows => _windows;
|
||||
public IBindingsContext GraphicsBindingContext => _mainGraphicsContext;
|
||||
|
||||
public WindowReg? MainWindow => _mainWindow;
|
||||
private GlfwWindowReg? _mainWindow;
|
||||
private GlfwBindingsContext _mainGraphicsContext = default!;
|
||||
private int _nextWindowId = 1;
|
||||
|
||||
public async Task<WindowHandle> WindowCreate(WindowCreateParameters parameters)
|
||||
{
|
||||
// tfw await not allowed in unsafe contexts
|
||||
|
||||
// GL APIs don't take kindly to making a new window without unbinding the main context. Great.
|
||||
// Leaving code for async path in, in case it works on like GLX.
|
||||
var unbindContextAndBlock = true;
|
||||
|
||||
DebugTools.AssertNotNull(_mainWindow);
|
||||
|
||||
Task<GlfwWindowCreateResult> task;
|
||||
unsafe
|
||||
{
|
||||
if (unbindContextAndBlock)
|
||||
GLFW.MakeContextCurrent(null);
|
||||
|
||||
task = SharedWindowCreate(
|
||||
_clyde._chosenRenderer,
|
||||
parameters,
|
||||
_mainWindow!.GlfwWindow);
|
||||
}
|
||||
|
||||
if (unbindContextAndBlock)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
// Block the main thread (to avoid stuff like texture uploads being problematic).
|
||||
WaitWindowCreate(task);
|
||||
|
||||
if (unbindContextAndBlock)
|
||||
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await task;
|
||||
}
|
||||
|
||||
var (reg, error) = await task;
|
||||
|
||||
if (reg == null)
|
||||
{
|
||||
var (desc, errCode) = error!.Value;
|
||||
throw new GlfwException($"{errCode}: {desc}");
|
||||
}
|
||||
|
||||
_clyde.CreateWindowRenderTexture(reg);
|
||||
_clyde.InitWindowBlitThread(reg);
|
||||
|
||||
unsafe
|
||||
{
|
||||
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
|
||||
}
|
||||
|
||||
return reg.Handle;
|
||||
}
|
||||
}
|
||||
|
||||
// Yes, you read that right.
|
||||
private sealed unsafe partial class GlfwWindowingImpl
|
||||
{
|
||||
public bool TryInitMainWindow(Renderer renderer, [NotNullWhen(false)] out string? error)
|
||||
{
|
||||
var width = _cfg.GetCVar(CVars.DisplayWidth);
|
||||
var height = _cfg.GetCVar(CVars.DisplayHeight);
|
||||
var prevWidth = width;
|
||||
var prevHeight = height;
|
||||
|
||||
IClydeMonitor? monitor = null;
|
||||
var fullscreen = false;
|
||||
|
||||
if (_clyde._windowMode == WindowMode.Fullscreen)
|
||||
{
|
||||
monitor = _monitors[_primaryMonitorId].Handle;
|
||||
width = monitor.Size.X;
|
||||
height = monitor.Size.Y;
|
||||
fullscreen = true;
|
||||
}
|
||||
|
||||
var parameters = new WindowCreateParameters
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
Monitor = monitor,
|
||||
Fullscreen = fullscreen
|
||||
};
|
||||
|
||||
var windowTask = SharedWindowCreate(renderer, parameters, null);
|
||||
WaitWindowCreate(windowTask);
|
||||
|
||||
var (reg, err) = windowTask.Result;
|
||||
if (reg == null)
|
||||
{
|
||||
var (desc, code) = err!.Value;
|
||||
error = $"[{code}] {desc}";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DebugTools.Assert(reg.Id == WindowId.Main);
|
||||
|
||||
_mainWindow = reg;
|
||||
reg.IsMainWindow = true;
|
||||
|
||||
if (fullscreen)
|
||||
{
|
||||
reg.PrevWindowSize = (prevWidth, prevHeight);
|
||||
reg.PrevWindowPos = (50, 50);
|
||||
}
|
||||
|
||||
UpdateVSync();
|
||||
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void WaitWindowCreate(Task<GlfwWindowCreateResult> windowTask)
|
||||
{
|
||||
while (!windowTask.IsCompleted)
|
||||
{
|
||||
// Keep processing events until the window task gives either an error or success.
|
||||
WaitEvents();
|
||||
ProcessEvents(single: true);
|
||||
}
|
||||
}
|
||||
|
||||
private Task<GlfwWindowCreateResult> SharedWindowCreate(
|
||||
Renderer renderer,
|
||||
WindowCreateParameters parameters,
|
||||
Window* share)
|
||||
{
|
||||
// Yes we ping-pong this TCS through the window thread and back, deal with it.
|
||||
var tcs = new TaskCompletionSource<GlfwWindowCreateResult>();
|
||||
SendCmd(new CmdWinCreate(
|
||||
renderer,
|
||||
parameters,
|
||||
(nint) share,
|
||||
tcs));
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
private void FinishWindowCreate(EventWindowCreate ev)
|
||||
{
|
||||
var (res, tcs) = ev;
|
||||
var reg = res.Reg;
|
||||
|
||||
if (reg != null)
|
||||
{
|
||||
_windows.Add(reg);
|
||||
_clyde._windowHandles.Add(reg.Handle);
|
||||
}
|
||||
|
||||
tcs.TrySetResult(res);
|
||||
}
|
||||
|
||||
private void WinThreadWinCreate(CmdWinCreate cmd)
|
||||
{
|
||||
var (renderer, parameters, share, tcs) = cmd;
|
||||
|
||||
var window = CreateGlfwWindowForRenderer(renderer, parameters, (Window*) share);
|
||||
|
||||
if (window == null)
|
||||
{
|
||||
var err = GLFW.GetError(out var desc);
|
||||
|
||||
SendEvent(new EventWindowCreate(new GlfwWindowCreateResult(null, (desc, err)), tcs));
|
||||
return;
|
||||
}
|
||||
|
||||
// We can't invoke the TCS directly from the windowing thread because:
|
||||
// * it'd hit the synchronization context,
|
||||
// which would make (blocking) main window init more annoying.
|
||||
// * it'd not be synchronized to other incoming window events correctly which might be icky.
|
||||
// So we send the TCS back to the game thread
|
||||
// which processes events in the correct order and has better control of stuff during init.
|
||||
var reg = WinThreadSetupWindow(window);
|
||||
|
||||
SendEvent(new EventWindowCreate(new GlfwWindowCreateResult(reg, null), tcs));
|
||||
}
|
||||
|
||||
private void WinThreadWinDestroy(CmdWinDestroy cmd)
|
||||
{
|
||||
GLFW.DestroyWindow((Window*) cmd.Window);
|
||||
}
|
||||
|
||||
public void WindowSetTitle(WindowReg window, string title)
|
||||
{
|
||||
CheckWindowDisposed(window);
|
||||
|
||||
if (title == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(title));
|
||||
}
|
||||
|
||||
var reg = (GlfwWindowReg) window;
|
||||
|
||||
SendCmd(new CmdWinSetTitle((nint) reg.GlfwWindow, title));
|
||||
}
|
||||
|
||||
private void WinThreadWinSetTitle(CmdWinSetTitle cmd)
|
||||
{
|
||||
GLFW.SetWindowTitle((Window*) cmd.Window, cmd.Title);
|
||||
}
|
||||
|
||||
public void WindowSetMonitor(WindowReg window, IClydeMonitor monitor)
|
||||
{
|
||||
CheckWindowDisposed(window);
|
||||
|
||||
var winReg = (GlfwWindowReg) window;
|
||||
|
||||
var monitorImpl = (MonitorHandle) monitor;
|
||||
|
||||
SendCmd(new CmdWinSetMonitor(
|
||||
(nint) winReg.GlfwWindow,
|
||||
monitorImpl.Id,
|
||||
0, 0,
|
||||
monitorImpl.Size.X, monitorImpl.Size.Y,
|
||||
monitorImpl.RefreshRate));
|
||||
}
|
||||
|
||||
private void WinThreadWinSetMonitor(CmdWinSetMonitor cmd)
|
||||
{
|
||||
Monitor* monitorPtr;
|
||||
if (cmd.MonitorId == 0)
|
||||
{
|
||||
monitorPtr = null;
|
||||
}
|
||||
else if (_winThreadMonitors.TryGetValue(cmd.MonitorId, out var monitorReg))
|
||||
{
|
||||
monitorPtr = monitorReg.Ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GLFW.SetWindowMonitor(
|
||||
(Window*) cmd.Window,
|
||||
monitorPtr,
|
||||
cmd.X, cmd.Y,
|
||||
cmd.W, cmd.H,
|
||||
cmd.RefreshRate
|
||||
);
|
||||
}
|
||||
|
||||
public void WindowSetVisible(WindowReg window, bool visible)
|
||||
{
|
||||
var reg = (GlfwWindowReg) window;
|
||||
reg.IsVisible = visible;
|
||||
|
||||
SendCmd(new CmdWinSetVisible((nint) reg.GlfwWindow, visible));
|
||||
}
|
||||
|
||||
private void WinThreadWinSetVisible(CmdWinSetVisible cmd)
|
||||
{
|
||||
var win = (Window*) cmd.Window;
|
||||
|
||||
if (cmd.Visible)
|
||||
{
|
||||
GLFW.ShowWindow(win);
|
||||
}
|
||||
else
|
||||
{
|
||||
GLFW.HideWindow(win);
|
||||
}
|
||||
}
|
||||
|
||||
public void WindowRequestAttention(WindowReg window)
|
||||
{
|
||||
CheckWindowDisposed(window);
|
||||
|
||||
var reg = (GlfwWindowReg) window;
|
||||
|
||||
SendCmd(new CmdWinRequestAttention((nint) reg.GlfwWindow));
|
||||
}
|
||||
|
||||
private void WinThreadWinRequestAttention(CmdWinRequestAttention cmd)
|
||||
{
|
||||
var win = (Window*) cmd.Window;
|
||||
|
||||
GLFW.RequestWindowAttention(win);
|
||||
}
|
||||
|
||||
public void WindowSwapBuffers(WindowReg window)
|
||||
{
|
||||
CheckWindowDisposed(window);
|
||||
|
||||
var reg = (GlfwWindowReg) window;
|
||||
|
||||
GLFW.SwapBuffers(reg.GlfwWindow);
|
||||
}
|
||||
|
||||
public void UpdateVSync()
|
||||
{
|
||||
if (_mainWindow == null)
|
||||
return;
|
||||
|
||||
GLFW.MakeContextCurrent(_mainWindow!.GlfwWindow);
|
||||
GLFW.SwapInterval(_clyde._vSync ? 1 : 0);
|
||||
}
|
||||
|
||||
public void UpdateMainWindowMode()
|
||||
{
|
||||
if (_mainWindow == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var win = _mainWindow;
|
||||
if (_clyde._windowMode == WindowMode.Fullscreen)
|
||||
{
|
||||
_mainWindow.PrevWindowSize = win.WindowSize;
|
||||
_mainWindow.PrevWindowPos = win.PrevWindowPos;
|
||||
|
||||
SendCmd(new CmdWinSetFullscreen((nint) _mainWindow.GlfwWindow));
|
||||
}
|
||||
else
|
||||
{
|
||||
SendCmd(new CmdWinSetMonitor(
|
||||
(nint) _mainWindow.GlfwWindow,
|
||||
0,
|
||||
_mainWindow.PrevWindowPos.X, _mainWindow.PrevWindowPos.Y,
|
||||
_mainWindow.PrevWindowSize.X, _mainWindow.PrevWindowSize.Y,
|
||||
0
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private void WinThreadWinSetFullscreen(CmdWinSetFullscreen cmd)
|
||||
{
|
||||
var ptr = (Window*) cmd.Window;
|
||||
GLFW.GetWindowSize(ptr, out var w, out var h);
|
||||
GLFW.GetWindowPos(ptr, out var x, out var y);
|
||||
|
||||
var monitor = MonitorForWindow(ptr);
|
||||
var mode = GLFW.GetVideoMode(monitor);
|
||||
|
||||
GLFW.SetWindowMonitor(
|
||||
ptr,
|
||||
monitor,
|
||||
0, 0,
|
||||
mode->Width, mode->Height,
|
||||
mode->RefreshRate);
|
||||
}
|
||||
|
||||
// glfwGetWindowMonitor only works for fullscreen windows.
|
||||
// Picks the monitor with the top-left corner of the window.
|
||||
private Monitor* MonitorForWindow(Window* window)
|
||||
{
|
||||
GLFW.GetWindowPos(window, out var winPosX, out var winPosY);
|
||||
var monitors = GLFW.GetMonitorsRaw(out var count);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var monitor = monitors[i];
|
||||
GLFW.GetMonitorPos(monitor, out var monPosX, out var monPosY);
|
||||
var videoMode = GLFW.GetVideoMode(monitor);
|
||||
|
||||
var box = Box2i.FromDimensions(monPosX, monPosY, videoMode->Width, videoMode->Height);
|
||||
if (box.Contains(winPosX, winPosY))
|
||||
return monitor;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return GLFW.GetPrimaryMonitor();
|
||||
}
|
||||
|
||||
public uint? WindowGetX11Id(WindowReg window)
|
||||
{
|
||||
CheckWindowDisposed(window);
|
||||
|
||||
var reg = (GlfwWindowReg) window;
|
||||
try
|
||||
{
|
||||
return GLFW.GetX11Window(reg.GlfwWindow);
|
||||
}
|
||||
catch (EntryPointNotFoundException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void WindowDestroy(WindowReg window)
|
||||
{
|
||||
var reg = (GlfwWindowReg) window;
|
||||
if (reg.IsDisposed)
|
||||
return;
|
||||
|
||||
reg.IsDisposed = true;
|
||||
|
||||
SendCmd(new CmdWinDestroy((nint) reg.GlfwWindow));
|
||||
|
||||
_windows.Remove(reg);
|
||||
_clyde._windowHandles.Remove(reg.Handle);
|
||||
|
||||
_clyde.DestroyWindow?.Invoke(new WindowDestroyedEventArgs(window.Handle));
|
||||
}
|
||||
|
||||
private Window* CreateGlfwWindowForRenderer(
|
||||
Renderer r,
|
||||
WindowCreateParameters parameters,
|
||||
Window* contextShare)
|
||||
{
|
||||
#if DEBUG
|
||||
GLFW.WindowHint(WindowHintBool.OpenGLDebugContext, true);
|
||||
#endif
|
||||
GLFW.WindowHint(WindowHintString.X11ClassName, "SS14");
|
||||
GLFW.WindowHint(WindowHintString.X11InstanceName, "SS14");
|
||||
|
||||
if (r == Renderer.OpenGL33)
|
||||
{
|
||||
GLFW.WindowHint(WindowHintInt.ContextVersionMajor, 3);
|
||||
GLFW.WindowHint(WindowHintInt.ContextVersionMinor, 3);
|
||||
GLFW.WindowHint(WindowHintBool.OpenGLForwardCompat, true);
|
||||
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGlApi);
|
||||
GLFW.WindowHint(WindowHintContextApi.ContextCreationApi, ContextApi.NativeContextApi);
|
||||
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Core);
|
||||
GLFW.WindowHint(WindowHintBool.SrgbCapable, true);
|
||||
}
|
||||
else if (r == Renderer.OpenGL31)
|
||||
{
|
||||
GLFW.WindowHint(WindowHintInt.ContextVersionMajor, 3);
|
||||
GLFW.WindowHint(WindowHintInt.ContextVersionMinor, 1);
|
||||
GLFW.WindowHint(WindowHintBool.OpenGLForwardCompat, false);
|
||||
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGlApi);
|
||||
GLFW.WindowHint(WindowHintContextApi.ContextCreationApi, ContextApi.NativeContextApi);
|
||||
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Any);
|
||||
GLFW.WindowHint(WindowHintBool.SrgbCapable, true);
|
||||
}
|
||||
else if (r == Renderer.OpenGLES2)
|
||||
{
|
||||
GLFW.WindowHint(WindowHintInt.ContextVersionMajor, 2);
|
||||
GLFW.WindowHint(WindowHintInt.ContextVersionMinor, 0);
|
||||
GLFW.WindowHint(WindowHintBool.OpenGLForwardCompat, true);
|
||||
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGlEsApi);
|
||||
// GLES2 is initialized through EGL to allow ANGLE usage.
|
||||
// (It may be an idea to make this a configuration cvar)
|
||||
GLFW.WindowHint(WindowHintContextApi.ContextCreationApi, ContextApi.EglContextApi);
|
||||
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Any);
|
||||
GLFW.WindowHint(WindowHintBool.SrgbCapable, false);
|
||||
}
|
||||
|
||||
|
||||
Monitor* monitor = null;
|
||||
if (parameters.Monitor != null &&
|
||||
_winThreadMonitors.TryGetValue(parameters.Monitor.Id, out var monitorReg))
|
||||
{
|
||||
monitor = monitorReg.Ptr;
|
||||
}
|
||||
|
||||
GLFW.WindowHint(WindowHintBool.Visible, false);
|
||||
|
||||
var window = GLFW.CreateWindow(
|
||||
parameters.Width, parameters.Height,
|
||||
parameters.Title,
|
||||
parameters.Fullscreen ? monitor : null,
|
||||
contextShare);
|
||||
|
||||
// Check if window failed to create.
|
||||
if (window == null)
|
||||
return null;
|
||||
|
||||
if (parameters.Maximized)
|
||||
{
|
||||
GLFW.GetMonitorPos(monitor, out var x, out var y);
|
||||
GLFW.SetWindowPos(window, x, y);
|
||||
GLFW.MaximizeWindow(window);
|
||||
}
|
||||
|
||||
if (parameters.Visible)
|
||||
{
|
||||
GLFW.ShowWindow(window);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
private GlfwWindowReg WinThreadSetupWindow(Window* window)
|
||||
{
|
||||
var reg = new GlfwWindowReg
|
||||
{
|
||||
GlfwWindow = window,
|
||||
Id = new WindowId(_nextWindowId++)
|
||||
};
|
||||
var handle = new WindowHandle(_clyde, reg);
|
||||
reg.Handle = handle;
|
||||
|
||||
LoadWindowIcon(window);
|
||||
|
||||
GLFW.SetCharCallback(window, _charCallback);
|
||||
GLFW.SetKeyCallback(window, _keyCallback);
|
||||
GLFW.SetWindowCloseCallback(window, _windowCloseCallback);
|
||||
GLFW.SetCursorPosCallback(window, _cursorPosCallback);
|
||||
GLFW.SetCursorEnterCallback(window, _cursorEnterCallback);
|
||||
GLFW.SetWindowSizeCallback(window, _windowSizeCallback);
|
||||
GLFW.SetWindowPosCallback(window, _windowPosCallback);
|
||||
GLFW.SetScrollCallback(window, _scrollCallback);
|
||||
GLFW.SetMouseButtonCallback(window, _mouseButtonCallback);
|
||||
GLFW.SetWindowContentScaleCallback(window, _windowContentScaleCallback);
|
||||
GLFW.SetWindowIconifyCallback(window, _windowIconifyCallback);
|
||||
GLFW.SetWindowFocusCallback(window, _windowFocusCallback);
|
||||
|
||||
GLFW.GetFramebufferSize(window, out var fbW, out var fbH);
|
||||
reg.FramebufferSize = (fbW, fbH);
|
||||
|
||||
GLFW.GetWindowContentScale(window, out var scaleX, out var scaleY);
|
||||
reg.WindowScale = (scaleX, scaleY);
|
||||
|
||||
GLFW.GetWindowSize(window, out var w, out var h);
|
||||
reg.PrevWindowSize = reg.WindowSize = (w, h);
|
||||
|
||||
GLFW.GetWindowPos(window, out var x, out var y);
|
||||
reg.PrevWindowPos = (x, y);
|
||||
|
||||
reg.PixelRatio = reg.FramebufferSize / reg.WindowSize;
|
||||
|
||||
return reg;
|
||||
}
|
||||
|
||||
private WindowReg? FindWindow(nint window) => FindWindow((Window*) window);
|
||||
|
||||
private WindowReg? FindWindow(Window* window)
|
||||
{
|
||||
foreach (var windowReg in _windows)
|
||||
{
|
||||
if (windowReg.GlfwWindow == window)
|
||||
{
|
||||
return windowReg;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public int KeyGetScanCode(Keyboard.Key key)
|
||||
{
|
||||
return GLFW.GetKeyScancode(ConvertGlfwKeyReverse(key));
|
||||
}
|
||||
|
||||
public string KeyGetNameScanCode(int scanCode)
|
||||
{
|
||||
return GLFW.GetKeyName(Keys.Unknown, scanCode);
|
||||
}
|
||||
|
||||
public Task<string> ClipboardGetText()
|
||||
{
|
||||
var tcs = new TaskCompletionSource<string>();
|
||||
SendCmd(new CmdGetClipboard((nint) _mainWindow!.GlfwWindow, tcs));
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
private void WinThreadGetClipboard(CmdGetClipboard cmd)
|
||||
{
|
||||
var clipboard = GLFW.GetClipboardString((Window*) cmd.Window);
|
||||
// Don't have to care about synchronization I don't think so just fire this immediately.
|
||||
cmd.Tcs.TrySetResult(clipboard);
|
||||
}
|
||||
|
||||
public void ClipboardSetText(string text)
|
||||
{
|
||||
SendCmd(new CmdSetClipboard((nint) _mainWindow!.GlfwWindow, text));
|
||||
}
|
||||
|
||||
private void WinThreadSetClipboard(CmdSetClipboard cmd)
|
||||
{
|
||||
GLFW.SetClipboardString((Window*) cmd.Window, cmd.Text);
|
||||
}
|
||||
|
||||
public void LoadWindowIcon(Window* window)
|
||||
{
|
||||
var icons = _clyde.LoadWindowIcons().ToArray();
|
||||
|
||||
// Turn each image into a byte[] so we can actually pin their contents.
|
||||
// Wish I knew a clean way to do this without allocations.
|
||||
var images = icons
|
||||
.Select(i => (MemoryMarshal.Cast<Rgba32, byte>(i.GetPixelSpan()).ToArray(), i.Width, i.Height))
|
||||
.ToList();
|
||||
|
||||
// ReSharper disable once SuggestVarOrType_Elsewhere
|
||||
Span<GCHandle> handles = stackalloc GCHandle[images.Count];
|
||||
Span<GlfwImage> glfwImages = stackalloc GlfwImage[images.Count];
|
||||
|
||||
for (var i = 0; i < images.Count; i++)
|
||||
{
|
||||
var image = images[i];
|
||||
handles[i] = GCHandle.Alloc(image.Item1, GCHandleType.Pinned);
|
||||
var addrOfPinnedObject = (byte*) handles[i].AddrOfPinnedObject();
|
||||
glfwImages[i] = new GlfwImage(image.Width, image.Height, addrOfPinnedObject);
|
||||
}
|
||||
|
||||
GLFW.SetWindowIcon(window, glfwImages);
|
||||
|
||||
foreach (var handle in handles)
|
||||
{
|
||||
handle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
public void GLInitMainContext(bool gles)
|
||||
{
|
||||
_mainGraphicsContext = new GlfwBindingsContext();
|
||||
GL.LoadBindings(_mainGraphicsContext);
|
||||
|
||||
if (gles)
|
||||
{
|
||||
// On GLES we use some OES and KHR functions so make sure to initialize them.
|
||||
OpenToolkit.Graphics.ES20.GL.LoadBindings(_mainGraphicsContext);
|
||||
}
|
||||
}
|
||||
|
||||
public void GLMakeContextCurrent(WindowReg window)
|
||||
{
|
||||
CheckWindowDisposed(window);
|
||||
|
||||
var reg = (GlfwWindowReg) window;
|
||||
|
||||
GLFW.MakeContextCurrent(reg.GlfwWindow);
|
||||
}
|
||||
|
||||
public void GLSwapInterval(int interval)
|
||||
{
|
||||
GLFW.SwapInterval(interval);
|
||||
}
|
||||
|
||||
private void CheckWindowDisposed(WindowReg reg)
|
||||
{
|
||||
if (reg.IsDisposed)
|
||||
throw new ObjectDisposedException("Window disposed");
|
||||
}
|
||||
|
||||
private sealed class GlfwWindowReg : WindowReg
|
||||
{
|
||||
public Window* GlfwWindow;
|
||||
|
||||
// Kept around to avoid it being GCd.
|
||||
public CursorImpl? Cursor;
|
||||
}
|
||||
|
||||
private class GlfwBindingsContext : IBindingsContext
|
||||
{
|
||||
public IntPtr GetProcAddress(string procName)
|
||||
{
|
||||
return GLFW.GetProcAddress(procName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
119
Robust.Client/Graphics/Clyde/Windowing/Glfw.cs
Normal file
119
Robust.Client/Graphics/Clyde/Windowing/Glfw.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using OpenToolkit.GraphicsLibraryFramework;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
private sealed partial class GlfwWindowingImpl : IWindowingImpl
|
||||
{
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
|
||||
private readonly Clyde _clyde;
|
||||
|
||||
private readonly ISawmill _sawmill;
|
||||
private readonly ISawmill _sawmillGlfw;
|
||||
|
||||
private bool _glfwInitialized;
|
||||
private bool _win32Experience;
|
||||
|
||||
public GlfwWindowingImpl(Clyde clyde)
|
||||
{
|
||||
_clyde = clyde;
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_sawmill = _logManager.GetSawmill("clyde.win");
|
||||
_sawmillGlfw = _logManager.GetSawmill("clyde.win.glfw");
|
||||
}
|
||||
|
||||
public bool Init()
|
||||
{
|
||||
#if DEBUG
|
||||
_cfg.OnValueChanged(CVars.DisplayWin32Experience, b => _win32Experience = b, true);
|
||||
#endif
|
||||
|
||||
InitChannels();
|
||||
|
||||
if (!InitGlfw())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SetupGlobalCallbacks();
|
||||
InitMonitors();
|
||||
InitCursors();
|
||||
InitKeyMap();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
if (_glfwInitialized)
|
||||
{
|
||||
_sawmill.Debug("Terminating GLFW.");
|
||||
GLFW.Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
public void FlushDispose()
|
||||
{
|
||||
// Not currently used
|
||||
}
|
||||
|
||||
private bool InitGlfw()
|
||||
{
|
||||
StoreCallbacks();
|
||||
|
||||
GLFW.SetErrorCallback(_errorCallback);
|
||||
if (!GLFW.Init())
|
||||
{
|
||||
var err = GLFW.GetError(out var desc);
|
||||
_sawmill.Fatal($"Failed to initialize GLFW! [{err}] {desc}");
|
||||
return false;
|
||||
}
|
||||
|
||||
_glfwInitialized = true;
|
||||
var version = GLFW.GetVersionString();
|
||||
_sawmill.Debug("GLFW initialized, version: {0}.", version);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnGlfwError(ErrorCode code, string description)
|
||||
{
|
||||
_sawmillGlfw.Error("GLFW Error: [{0}] {1}", code, description);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class GlfwException : Exception
|
||||
{
|
||||
public GlfwException()
|
||||
{
|
||||
}
|
||||
|
||||
public GlfwException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public GlfwException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected GlfwException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Robust.Client/Graphics/Clyde/Windowing/IWindowingImpl.cs
Normal file
58
Robust.Client/Graphics/Clyde/Windowing/IWindowingImpl.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using OpenToolkit;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
partial class Clyde
|
||||
{
|
||||
private interface IWindowingImpl
|
||||
{
|
||||
WindowReg? MainWindow { get; }
|
||||
IReadOnlyList<WindowReg> AllWindows { get; }
|
||||
IBindingsContext GraphicsBindingContext { get; }
|
||||
|
||||
// Lifecycle stuff
|
||||
bool Init();
|
||||
bool TryInitMainWindow(Renderer renderer, [NotNullWhen(false)] out string? error);
|
||||
void Shutdown();
|
||||
|
||||
void EnterWindowLoop();
|
||||
void TerminateWindowLoop();
|
||||
|
||||
void ProcessEvents(bool single=false);
|
||||
void FlushDispose();
|
||||
|
||||
ICursor CursorGetStandard(StandardCursorShape shape);
|
||||
ICursor CursorCreate(Image<Rgba32> image, Vector2i hotSpot);
|
||||
void CursorSet(WindowReg window, ICursor? cursor);
|
||||
|
||||
void WindowSetTitle(WindowReg window, string title);
|
||||
void WindowSetMonitor(WindowReg window, IClydeMonitor monitor);
|
||||
void WindowSetVisible(WindowReg window, bool visible);
|
||||
void WindowRequestAttention(WindowReg window);
|
||||
void WindowSwapBuffers(WindowReg window);
|
||||
uint? WindowGetX11Id(WindowReg window);
|
||||
Task<WindowHandle> WindowCreate(WindowCreateParameters parameters);
|
||||
void WindowDestroy(WindowReg reg);
|
||||
|
||||
string KeyGetName(Keyboard.Key key);
|
||||
|
||||
Task<string> ClipboardGetText();
|
||||
void ClipboardSetText(string text);
|
||||
|
||||
void UpdateVSync();
|
||||
void UpdateMainWindowMode();
|
||||
|
||||
// OpenGL-related stuff.
|
||||
void GLMakeContextCurrent(WindowReg reg);
|
||||
void GLSwapInterval(int interval);
|
||||
void GLInitMainContext(bool gles);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
using System;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
@@ -13,80 +9,10 @@ namespace Robust.Client.Graphics
|
||||
// Maybe add borderless? Not sure how good Godot's default fullscreen is with alt tabbing.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages the game window, resolutions, fullscreen mode, VSync, etc...
|
||||
/// </summary>
|
||||
internal abstract class ClydeBase
|
||||
// Remember when this was called DisplayManager?
|
||||
internal static class ClydeBase
|
||||
{
|
||||
[Dependency] protected readonly IConfigurationManager ConfigurationManager = default!;
|
||||
|
||||
protected WindowMode WindowMode { get; private set; } = WindowMode.Windowed;
|
||||
protected bool VSync { get; private set; } = true;
|
||||
|
||||
public abstract Vector2i ScreenSize { get; }
|
||||
public abstract bool IsFocused { get; }
|
||||
|
||||
public abstract void SetWindowTitle(string title);
|
||||
|
||||
public virtual bool Initialize()
|
||||
{
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplayVSync, _vSyncChanged, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplayWindowMode, _windowModeChanged, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplayLightMapDivider, LightmapDividerChanged, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplayMaxLightsPerScene, MaxLightsPerSceneChanged, true);
|
||||
ConfigurationManager.OnValueChanged(CVars.DisplaySoftShadows, SoftShadowsChanged, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual void ReloadConfig()
|
||||
{
|
||||
ReadConfig();
|
||||
}
|
||||
|
||||
public abstract event Action<WindowResizedEventArgs> OnWindowResized;
|
||||
|
||||
public abstract event Action<WindowFocusedEventArgs> OnWindowFocused;
|
||||
|
||||
protected virtual void ReadConfig()
|
||||
{
|
||||
WindowMode = (WindowMode) ConfigurationManager.GetCVar(CVars.DisplayWindowMode);
|
||||
VSync = ConfigurationManager.GetCVar(CVars.DisplayVSync);
|
||||
}
|
||||
|
||||
private void _vSyncChanged(bool newValue)
|
||||
{
|
||||
VSync = newValue;
|
||||
VSyncChanged();
|
||||
}
|
||||
|
||||
protected virtual void VSyncChanged()
|
||||
{
|
||||
}
|
||||
|
||||
private void _windowModeChanged(int newValue)
|
||||
{
|
||||
WindowMode = (WindowMode) newValue;
|
||||
WindowModeChanged();
|
||||
}
|
||||
|
||||
protected virtual void WindowModeChanged()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void LightmapDividerChanged(int newValue)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void MaxLightsPerSceneChanged(int newValue)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void SoftShadowsChanged(bool newValue)
|
||||
{
|
||||
}
|
||||
|
||||
protected static Vector2i ClampSubRegion(Vector2i size, UIBox2i? subRegionSpecified)
|
||||
internal static Vector2i ClampSubRegion(Vector2i size, UIBox2i? subRegionSpecified)
|
||||
{
|
||||
return subRegionSpecified == null
|
||||
? size
|
||||
|
||||
@@ -153,6 +153,17 @@ namespace Robust.Client.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
public Thickness Padding
|
||||
{
|
||||
set
|
||||
{
|
||||
PaddingLeft = value.Left;
|
||||
PaddingTop = value.Top;
|
||||
PaddingRight = value.Right;
|
||||
PaddingBottom = value.Bottom;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw this style box to the screen at the specified coordinates.
|
||||
/// </summary>
|
||||
|
||||
@@ -12,12 +12,15 @@ namespace Robust.Client.Graphics
|
||||
|
||||
public interface IClyde
|
||||
{
|
||||
IRenderWindow MainWindowRenderTarget { get; }
|
||||
IClydeWindow MainWindow { get; }
|
||||
IRenderTarget MainWindowRenderTarget => MainWindow.RenderTarget;
|
||||
|
||||
Vector2i ScreenSize { get; }
|
||||
|
||||
bool IsFocused { get; }
|
||||
|
||||
IEnumerable<IClydeWindow> AllWindows { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The default scale ratio for window contents, given to us by the OS.
|
||||
/// </summary>
|
||||
@@ -35,7 +38,7 @@ namespace Robust.Client.Graphics
|
||||
|
||||
event Action<WindowFocusedEventArgs> OnWindowFocused;
|
||||
|
||||
event Action OnWindowScaleChanged;
|
||||
event Action<WindowContentScaleEventArgs> OnWindowScaleChanged;
|
||||
|
||||
OwnedTexture LoadTextureFromPNGStream(Stream stream, string? name = null,
|
||||
TextureLoadParameters? loadParams = null);
|
||||
@@ -126,7 +129,7 @@ namespace Robust.Client.Graphics
|
||||
IClydeViewport CreateViewport(Vector2i size, TextureSampleParameters? sampleParameters, string? name = null);
|
||||
|
||||
IEnumerable<IClydeMonitor> EnumerateMonitors();
|
||||
}
|
||||
|
||||
// TODO: Maybe implement IDisposable for render targets. I got lazy and didn't.
|
||||
Task<IClydeWindow> CreateWindow(WindowCreateParameters parameters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Robust.Client.Graphics
|
||||
// AUDIO SYSTEM DOWN BELOW.
|
||||
AudioStream LoadAudioOggVorbis(Stream stream, string? name = null);
|
||||
AudioStream LoadAudioWav(Stream stream, string? name = null);
|
||||
AudioStream LoadAudioRaw(short[] samples, int channels, int sampleRate);
|
||||
|
||||
void SetMasterVolume(float newVolume);
|
||||
|
||||
|
||||
@@ -21,5 +21,6 @@ namespace Robust.Client.Graphics
|
||||
void SetOcclusion(float blocks);
|
||||
void SetPlaybackPosition(float seconds);
|
||||
void SetVelocity(Vector2 velocity);
|
||||
void SetVolumeDirect(float masterVolumeDecay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -14,15 +15,21 @@ namespace Robust.Client.Graphics
|
||||
void ProcessInput(FrameEventArgs frameEventArgs);
|
||||
|
||||
// Init.
|
||||
bool Initialize();
|
||||
bool SeparateWindowThread { get; }
|
||||
bool InitializePreWindowing();
|
||||
void EnterWindowLoop();
|
||||
bool InitializePostWindowing();
|
||||
void Ready();
|
||||
void TerminateWindowLoop();
|
||||
|
||||
event Action<TextEventArgs> TextEntered;
|
||||
event Action<MouseMoveEventArgs> MouseMove;
|
||||
event Action<MouseEnterLeaveEventArgs> MouseEnterLeave;
|
||||
event Action<KeyEventArgs> KeyUp;
|
||||
event Action<KeyEventArgs> KeyDown;
|
||||
event Action<MouseWheelEventArgs> MouseWheel;
|
||||
event Action<string> CloseWindow;
|
||||
event Action<WindowClosedEventArgs> CloseWindow;
|
||||
event Action<WindowDestroyedEventArgs> DestroyWindow;
|
||||
|
||||
ClydeHandle LoadShader(ParsedShader shader, string? name = null);
|
||||
|
||||
@@ -37,7 +44,7 @@ namespace Robust.Client.Graphics
|
||||
/// <summary>
|
||||
/// This is purely a hook for <see cref="IInputManager"/>, use that instead.
|
||||
/// </summary>
|
||||
Vector2 MouseScreenPosition { get; }
|
||||
ScreenCoordinates MouseScreenPosition { get; }
|
||||
|
||||
IClydeDebugInfo DebugInfo { get; }
|
||||
|
||||
@@ -48,9 +55,7 @@ namespace Robust.Client.Graphics
|
||||
ClydeDebugLayers DebugLayers { get; set; }
|
||||
|
||||
string GetKeyName(Keyboard.Key key);
|
||||
string GetKeyNameScanCode(int scanCode);
|
||||
|
||||
int GetKeyScanCode(Keyboard.Key key);
|
||||
void Shutdown();
|
||||
|
||||
/// <returns>Null if not running on X11.</returns>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Maths;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
@@ -14,5 +15,7 @@ namespace Robust.Client.Graphics
|
||||
string Name { get; }
|
||||
Vector2i Size { get; }
|
||||
int RefreshRate { get; }
|
||||
|
||||
IEnumerable<VideoMode> VideoModes { get; }
|
||||
}
|
||||
}
|
||||
|
||||
33
Robust.Client/Graphics/IClydeWindow.cs
Normal file
33
Robust.Client/Graphics/IClydeWindow.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single operating system window.
|
||||
/// </summary>
|
||||
public interface IClydeWindow : IDisposable
|
||||
{
|
||||
bool IsDisposed { get; }
|
||||
WindowId Id { get; }
|
||||
IRenderTarget RenderTarget { get; }
|
||||
string Title { get; set; }
|
||||
Vector2i Size { get; }
|
||||
bool IsFocused { get; }
|
||||
bool IsMinimized { get; }
|
||||
bool IsVisible { get; set; }
|
||||
Vector2 ContentScale { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, the user closing the window will also <see cref="IDisposable.Dispose"/> it.
|
||||
/// </summary>
|
||||
bool DisposeOnClose { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the user tries to close the window. Note that if <see cref="DisposeOnClose"/> is not true,
|
||||
/// this is merely a request and the user pressing the close button does nothing.
|
||||
/// </summary>
|
||||
event Action<WindowClosedEventArgs> Closed;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
internal interface IRenderHandle
|
||||
public interface IRenderHandle
|
||||
{
|
||||
DrawingHandleScreen DrawingHandleScreen { get; }
|
||||
DrawingHandleWorld DrawingHandleWorld { get; }
|
||||
|
||||
void RenderInRenderTarget(IRenderTarget target, Action a);
|
||||
|
||||
void SetScissor(UIBox2i? scissorBox);
|
||||
void DrawEntity(IEntity entity, Vector2 position, Vector2 scale, Direction? overrideDirection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// A render target that represents the contents of an OS window.
|
||||
/// </summary>
|
||||
public interface IRenderWindow : IRenderTarget
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ namespace Robust.Client.Graphics
|
||||
|
||||
/// <summary>
|
||||
/// The viewport control that is rendering this viewport.
|
||||
/// Only available for screen-space overlays.
|
||||
/// Not always available.
|
||||
/// </summary>
|
||||
public readonly IViewportControl? ViewportControl;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Robust.Client.Graphics
|
||||
[ViewVariables]
|
||||
public Vector2i Size { get; private set; }
|
||||
[ViewVariables]
|
||||
private Dictionary<StateId, State> States = new();
|
||||
private Dictionary<StateId, State> States;
|
||||
|
||||
/// <summary>
|
||||
/// The original path of this RSI or null.
|
||||
@@ -44,10 +44,11 @@ namespace Robust.Client.Graphics
|
||||
return States.TryGetValue(stateId, out state);
|
||||
}
|
||||
|
||||
public RSI(Vector2i size, ResourcePath? path = null)
|
||||
public RSI(Vector2i size, ResourcePath? path = null, int capacity = 0)
|
||||
{
|
||||
Size = size;
|
||||
Path = path;
|
||||
States = new Dictionary<StateId, State>(capacity);
|
||||
}
|
||||
|
||||
public IEnumerator<State> GetEnumerator()
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Robust.Client.Graphics
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
[ViewVariables]
|
||||
[field: DataField("id", required: true)]
|
||||
[DataField("id", required: true)]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
private ShaderKind Kind;
|
||||
|
||||
12
Robust.Client/Graphics/VideoMode.cs
Normal file
12
Robust.Client/Graphics/VideoMode.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
public struct VideoMode
|
||||
{
|
||||
public ushort Width;
|
||||
public ushort Height;
|
||||
public ushort RefreshRate;
|
||||
public byte RedBits;
|
||||
public byte BlueBits;
|
||||
public byte GreenBits;
|
||||
}
|
||||
}
|
||||
14
Robust.Client/Graphics/WindowClosedEventArgs.cs
Normal file
14
Robust.Client/Graphics/WindowClosedEventArgs.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
public class WindowClosedEventArgs : EventArgs
|
||||
{
|
||||
public IClydeWindow Window { get; }
|
||||
|
||||
public WindowClosedEventArgs(IClydeWindow window)
|
||||
{
|
||||
Window = window;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Robust.Client/Graphics/WindowContentScaleEventArgs.cs
Normal file
14
Robust.Client/Graphics/WindowContentScaleEventArgs.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
public sealed class WindowContentScaleEventArgs : EventArgs
|
||||
{
|
||||
public WindowContentScaleEventArgs(IClydeWindow window)
|
||||
{
|
||||
Window = window;
|
||||
}
|
||||
|
||||
public IClydeWindow Window { get; }
|
||||
}
|
||||
}
|
||||
13
Robust.Client/Graphics/WindowCreateParameters.cs
Normal file
13
Robust.Client/Graphics/WindowCreateParameters.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
public sealed class WindowCreateParameters
|
||||
{
|
||||
public int Width = 1280;
|
||||
public int Height = 720;
|
||||
public string Title = "";
|
||||
public bool Maximized;
|
||||
public bool Visible = true;
|
||||
public IClydeMonitor? Monitor;
|
||||
public bool Fullscreen;
|
||||
}
|
||||
}
|
||||
14
Robust.Client/Graphics/WindowDestroyedEventArgs.cs
Normal file
14
Robust.Client/Graphics/WindowDestroyedEventArgs.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
public class WindowDestroyedEventArgs : EventArgs
|
||||
{
|
||||
public IClydeWindow Window { get; }
|
||||
|
||||
public WindowDestroyedEventArgs(IClydeWindow window)
|
||||
{
|
||||
Window = window;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,13 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
public class WindowFocusedEventArgs : EventArgs
|
||||
{
|
||||
public WindowFocusedEventArgs(bool focused)
|
||||
public WindowFocusedEventArgs(bool focused, IClydeWindow window)
|
||||
{
|
||||
Focused = focused;
|
||||
Window = window;
|
||||
}
|
||||
|
||||
public bool Focused { get; }
|
||||
public IClydeWindow Window { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,15 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
public class WindowResizedEventArgs : EventArgs
|
||||
{
|
||||
public WindowResizedEventArgs(Vector2i oldSize, Vector2i newSize)
|
||||
public WindowResizedEventArgs(Vector2i oldSize, Vector2i newSize, IClydeWindow window)
|
||||
{
|
||||
OldSize = oldSize;
|
||||
NewSize = newSize;
|
||||
Window = window;
|
||||
}
|
||||
|
||||
public Vector2i OldSize { get; }
|
||||
public Vector2i NewSize { get; }
|
||||
public IClydeWindow Window { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@ namespace Robust.Client
|
||||
bool ContentStart { get; set; }
|
||||
void SetCommandLineArgs(CommandLineArgs args);
|
||||
bool LoadConfigAndUserData { get; set; }
|
||||
bool Startup(Func<ILogHandler>? logHandlerFactory = null);
|
||||
void MainLoop(GameController.DisplayMode mode);
|
||||
void Run(GameController.DisplayMode mode, Func<ILogHandler>? logHandlerFactory = null);
|
||||
void KeyDown(KeyEventArgs keyEvent);
|
||||
void KeyUp(KeyEventArgs keyEvent);
|
||||
void TextEntered(TextEventArgs textEvent);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Input
|
||||
@@ -8,7 +9,7 @@ namespace Robust.Client.Input
|
||||
{
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
|
||||
public override Vector2 MouseScreenPosition => _clyde.MouseScreenPosition;
|
||||
public override ScreenCoordinates MouseScreenPosition => _clyde.MouseScreenPosition;
|
||||
|
||||
public override string GetKeyName(Keyboard.Key key)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Input
|
||||
@@ -87,9 +89,9 @@ namespace Robust.Client.Input
|
||||
/// <summary>
|
||||
/// Position of the mouse relative to the screen.
|
||||
/// </summary>
|
||||
public Vector2 Position { get; }
|
||||
public ScreenCoordinates Position { get; }
|
||||
|
||||
protected MouseEventArgs(Vector2 position)
|
||||
protected MouseEventArgs(ScreenCoordinates position)
|
||||
{
|
||||
Position = position;
|
||||
}
|
||||
@@ -103,7 +105,7 @@ namespace Robust.Client.Input
|
||||
public Mouse.Button Button { get; }
|
||||
|
||||
// ALL the parameters!
|
||||
public MouseButtonEventArgs(Mouse.Button button, Vector2 position)
|
||||
public MouseButtonEventArgs(Mouse.Button button, ScreenCoordinates position)
|
||||
: base(position)
|
||||
{
|
||||
Button = button;
|
||||
@@ -118,7 +120,7 @@ namespace Robust.Client.Input
|
||||
public Vector2 Delta { get; }
|
||||
|
||||
// ALL the parameters!
|
||||
public MouseWheelEventArgs(Vector2 delta, Vector2 position)
|
||||
public MouseWheelEventArgs(Vector2 delta, ScreenCoordinates position)
|
||||
: base(position)
|
||||
{
|
||||
Delta = delta;
|
||||
@@ -133,10 +135,27 @@ namespace Robust.Client.Input
|
||||
public Vector2 Relative { get; }
|
||||
|
||||
// ALL the parameters!
|
||||
public MouseMoveEventArgs(Vector2 relative, Vector2 position)
|
||||
public MouseMoveEventArgs(Vector2 relative, ScreenCoordinates position)
|
||||
: base(position)
|
||||
{
|
||||
Relative = relative;
|
||||
}
|
||||
}
|
||||
|
||||
public class MouseEnterLeaveEventArgs : EventArgs
|
||||
{
|
||||
public IClydeWindow Window { get; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the mouse ENTERED the window, false if it LEFT the window.
|
||||
/// </summary>
|
||||
public bool Entered { get; }
|
||||
|
||||
// ALL the parameters!
|
||||
public MouseEnterLeaveEventArgs(IClydeWindow window, bool entered)
|
||||
{
|
||||
Window = window;
|
||||
Entered = entered;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Client.Input
|
||||
{
|
||||
@@ -25,7 +25,10 @@ namespace Robust.Client.Input
|
||||
/// <param name="eventArgs">The key event args triggering the input.</param>
|
||||
void ViewportKeyEvent(Control? control, BoundKeyEventArgs eventArgs);
|
||||
|
||||
Vector2 MouseScreenPosition { get; }
|
||||
/// <summary>
|
||||
/// The position and window of the mouse.
|
||||
/// </summary>
|
||||
ScreenCoordinates MouseScreenPosition { get; }
|
||||
|
||||
BoundKeyMap NetworkBindMap { get; }
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using GlfwKey = OpenToolkit.GraphicsLibraryFramework.Keys;
|
||||
using GlfwButton = OpenToolkit.GraphicsLibraryFramework.MouseButton;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Client.Input
|
||||
{
|
||||
@@ -29,11 +29,6 @@ namespace Robust.Client.Input
|
||||
return _mouseKeyMap[button];
|
||||
}
|
||||
|
||||
public static Button ConvertGlfwButton(GlfwButton button)
|
||||
{
|
||||
return _openTKButtonMap[button];
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Button, Keyboard.Key> _mouseKeyMap = new()
|
||||
{
|
||||
{Button.Left, Keyboard.Key.MouseLeft},
|
||||
@@ -48,17 +43,6 @@ namespace Robust.Client.Input
|
||||
{Button.LastButton, Keyboard.Key.Unknown},
|
||||
};
|
||||
|
||||
private static readonly Dictionary<GlfwButton, Button> _openTKButtonMap = new()
|
||||
{
|
||||
{GlfwButton.Left, Button.Left},
|
||||
{GlfwButton.Middle, Button.Middle},
|
||||
{GlfwButton.Right, Button.Right},
|
||||
{GlfwButton.Button4, Button.Button4},
|
||||
{GlfwButton.Button5, Button.Button5},
|
||||
{GlfwButton.Button6, Button.Button6},
|
||||
{GlfwButton.Button7, Button.Button7},
|
||||
{GlfwButton.Button8, Button.Button8},
|
||||
};
|
||||
}
|
||||
|
||||
public static class Keyboard
|
||||
@@ -186,226 +170,27 @@ namespace Robust.Client.Input
|
||||
return key >= Key.MouseLeft && key <= Key.MouseButton9;
|
||||
}
|
||||
|
||||
internal static Key ConvertGlfwKey(GlfwKey key)
|
||||
{
|
||||
if (_glfwKeyMap.TryGetValue(key, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return Key.Unknown;
|
||||
}
|
||||
|
||||
internal static GlfwKey ConvertGlfwKeyReverse(Key key)
|
||||
{
|
||||
if (_glfwKeyMapReverse.TryGetValue(key, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return GlfwKey.Unknown;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a "nice" version of special unprintable keys such as <see cref="Key.Escape"/>.
|
||||
/// </summary>
|
||||
/// <returns><see langword="null"/> if there is no nice version of this special key.</returns>
|
||||
internal static string? GetSpecialKeyName(Key key)
|
||||
internal static string? GetSpecialKeyName(Key key, ILocalizationManager loc)
|
||||
{
|
||||
if (_keyNiceNameMap.TryGetValue(key, out var val))
|
||||
var locId = $"input-key-{key}";
|
||||
if (key == Key.LSystem || key == Key.RSystem)
|
||||
{
|
||||
return val;
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
locId += "-win";
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
locId += "-mac";
|
||||
else
|
||||
locId += "-linux";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
if (loc.TryGetString(locId, out var name))
|
||||
return name;
|
||||
|
||||
private static readonly Dictionary<GlfwKey, Key> _glfwKeyMap;
|
||||
private static readonly Dictionary<Key, GlfwKey> _glfwKeyMapReverse;
|
||||
|
||||
private static readonly Dictionary<Key, string> _keyNiceNameMap;
|
||||
|
||||
static Keyboard()
|
||||
{
|
||||
_glfwKeyMap = new Dictionary<GlfwKey, Key>
|
||||
{
|
||||
{GlfwKey.A, Key.A},
|
||||
{GlfwKey.B, Key.B},
|
||||
{GlfwKey.C, Key.C},
|
||||
{GlfwKey.D, Key.D},
|
||||
{GlfwKey.E, Key.E},
|
||||
{GlfwKey.F, Key.F},
|
||||
{GlfwKey.G, Key.G},
|
||||
{GlfwKey.H, Key.H},
|
||||
{GlfwKey.I, Key.I},
|
||||
{GlfwKey.J, Key.J},
|
||||
{GlfwKey.K, Key.K},
|
||||
{GlfwKey.L, Key.L},
|
||||
{GlfwKey.M, Key.M},
|
||||
{GlfwKey.N, Key.N},
|
||||
{GlfwKey.O, Key.O},
|
||||
{GlfwKey.P, Key.P},
|
||||
{GlfwKey.Q, Key.Q},
|
||||
{GlfwKey.R, Key.R},
|
||||
{GlfwKey.S, Key.S},
|
||||
{GlfwKey.T, Key.T},
|
||||
{GlfwKey.U, Key.U},
|
||||
{GlfwKey.V, Key.V},
|
||||
{GlfwKey.W, Key.W},
|
||||
{GlfwKey.X, Key.X},
|
||||
{GlfwKey.Y, Key.Y},
|
||||
{GlfwKey.Z, Key.Z},
|
||||
{GlfwKey.D0, Key.Num0},
|
||||
{GlfwKey.D1, Key.Num1},
|
||||
{GlfwKey.D2, Key.Num2},
|
||||
{GlfwKey.D3, Key.Num3},
|
||||
{GlfwKey.D4, Key.Num4},
|
||||
{GlfwKey.D5, Key.Num5},
|
||||
{GlfwKey.D6, Key.Num6},
|
||||
{GlfwKey.D7, Key.Num7},
|
||||
{GlfwKey.D8, Key.Num8},
|
||||
{GlfwKey.D9, Key.Num9},
|
||||
{GlfwKey.KeyPad0, Key.NumpadNum0},
|
||||
{GlfwKey.KeyPad1, Key.NumpadNum1},
|
||||
{GlfwKey.KeyPad2, Key.NumpadNum2},
|
||||
{GlfwKey.KeyPad3, Key.NumpadNum3},
|
||||
{GlfwKey.KeyPad4, Key.NumpadNum4},
|
||||
{GlfwKey.KeyPad5, Key.NumpadNum5},
|
||||
{GlfwKey.KeyPad6, Key.NumpadNum6},
|
||||
{GlfwKey.KeyPad7, Key.NumpadNum7},
|
||||
{GlfwKey.KeyPad8, Key.NumpadNum8},
|
||||
{GlfwKey.KeyPad9, Key.NumpadNum9},
|
||||
{GlfwKey.Escape, Key.Escape},
|
||||
{GlfwKey.LeftControl, Key.Control},
|
||||
{GlfwKey.RightControl, Key.Control},
|
||||
{GlfwKey.RightShift, Key.Shift},
|
||||
{GlfwKey.LeftShift, Key.Shift},
|
||||
{GlfwKey.LeftAlt, Key.Alt},
|
||||
{GlfwKey.RightAlt, Key.Alt},
|
||||
{GlfwKey.LeftSuper, Key.LSystem},
|
||||
{GlfwKey.RightSuper, Key.RSystem},
|
||||
{GlfwKey.Menu, Key.Menu},
|
||||
{GlfwKey.LeftBracket, Key.LBracket},
|
||||
{GlfwKey.RightBracket, Key.RBracket},
|
||||
{GlfwKey.Semicolon, Key.SemiColon},
|
||||
{GlfwKey.Comma, Key.Comma},
|
||||
{GlfwKey.Period, Key.Period},
|
||||
{GlfwKey.Apostrophe, Key.Apostrophe},
|
||||
{GlfwKey.Slash, Key.Slash},
|
||||
{GlfwKey.Backslash, Key.BackSlash},
|
||||
{GlfwKey.GraveAccent, Key.Tilde},
|
||||
{GlfwKey.Equal, Key.Equal},
|
||||
{GlfwKey.Space, Key.Space},
|
||||
{GlfwKey.Enter, Key.Return},
|
||||
{GlfwKey.KeyPadEnter, Key.NumpadEnter},
|
||||
{GlfwKey.Backspace, Key.BackSpace},
|
||||
{GlfwKey.Tab, Key.Tab},
|
||||
{GlfwKey.PageUp, Key.PageUp},
|
||||
{GlfwKey.PageDown, Key.PageDown},
|
||||
{GlfwKey.End, Key.End},
|
||||
{GlfwKey.Home, Key.Home},
|
||||
{GlfwKey.Insert, Key.Insert},
|
||||
{GlfwKey.Delete, Key.Delete},
|
||||
{GlfwKey.Minus, Key.Minus},
|
||||
{GlfwKey.KeyPadAdd, Key.NumpadAdd},
|
||||
{GlfwKey.KeyPadSubtract, Key.NumpadSubtract},
|
||||
{GlfwKey.KeyPadDivide, Key.NumpadDivide},
|
||||
{GlfwKey.KeyPadMultiply, Key.NumpadMultiply},
|
||||
{GlfwKey.KeyPadDecimal, Key.NumpadDecimal},
|
||||
{GlfwKey.Left, Key.Left},
|
||||
{GlfwKey.Right, Key.Right},
|
||||
{GlfwKey.Up, Key.Up},
|
||||
{GlfwKey.Down, Key.Down},
|
||||
{GlfwKey.F1, Key.F1},
|
||||
{GlfwKey.F2, Key.F2},
|
||||
{GlfwKey.F3, Key.F3},
|
||||
{GlfwKey.F4, Key.F4},
|
||||
{GlfwKey.F5, Key.F5},
|
||||
{GlfwKey.F6, Key.F6},
|
||||
{GlfwKey.F7, Key.F7},
|
||||
{GlfwKey.F8, Key.F8},
|
||||
{GlfwKey.F9, Key.F9},
|
||||
{GlfwKey.F10, Key.F10},
|
||||
{GlfwKey.F11, Key.F11},
|
||||
{GlfwKey.F12, Key.F12},
|
||||
{GlfwKey.F13, Key.F13},
|
||||
{GlfwKey.F14, Key.F14},
|
||||
{GlfwKey.F15, Key.F15},
|
||||
{GlfwKey.Pause, Key.Pause},
|
||||
};
|
||||
|
||||
_glfwKeyMapReverse = new Dictionary<Key, GlfwKey>();
|
||||
|
||||
foreach (var (key, value) in _glfwKeyMap)
|
||||
{
|
||||
_glfwKeyMapReverse[value] = key;
|
||||
}
|
||||
|
||||
_keyNiceNameMap = new Dictionary<Key, string>
|
||||
{
|
||||
{Key.Escape, "Escape"},
|
||||
{Key.Control, "Control"},
|
||||
{Key.Shift, "Shift"},
|
||||
{Key.Alt, "Alt"},
|
||||
{Key.Menu, "Menu"},
|
||||
{Key.F1, "F1"},
|
||||
{Key.F2, "F2"},
|
||||
{Key.F3, "F3"},
|
||||
{Key.F4, "F4"},
|
||||
{Key.F5, "F5"},
|
||||
{Key.F6, "F6"},
|
||||
{Key.F7, "F7"},
|
||||
{Key.F8, "F8"},
|
||||
{Key.F9, "F9"},
|
||||
{Key.F10, "F10"},
|
||||
{Key.F11, "F11"},
|
||||
{Key.F12, "F12"},
|
||||
{Key.F13, "F13"},
|
||||
{Key.F14, "F14"},
|
||||
{Key.F15, "F15"},
|
||||
{Key.Pause, "Pause"},
|
||||
{Key.Left, "Left"},
|
||||
{Key.Up, "Up"},
|
||||
{Key.Down, "Down"},
|
||||
{Key.Right, "Right"},
|
||||
{Key.Space, "Space"},
|
||||
{Key.Return, "Return"},
|
||||
{Key.NumpadEnter, "Num Enter"},
|
||||
{Key.BackSpace, "Backspace"},
|
||||
{Key.Tab, "Tab"},
|
||||
{Key.PageUp, "Page Up"},
|
||||
{Key.PageDown, "Page Down"},
|
||||
{Key.End, "End"},
|
||||
{Key.Home, "Home"},
|
||||
{Key.Insert, "Insert"},
|
||||
{Key.Delete, "Delete"},
|
||||
{Key.MouseLeft, "Mouse Left"},
|
||||
{Key.MouseRight, "Mouse Right"},
|
||||
{Key.MouseMiddle, "Mouse Middle"},
|
||||
{Key.MouseButton4, "Mouse 4"},
|
||||
{Key.MouseButton5, "Mouse 5"},
|
||||
{Key.MouseButton6, "Mouse 6"},
|
||||
{Key.MouseButton7, "Mouse 7"},
|
||||
{Key.MouseButton8, "Mouse 8"},
|
||||
{Key.MouseButton9, "Mouse 9"},
|
||||
};
|
||||
|
||||
// Have to adjust system key name depending on platform.
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
_keyNiceNameMap.Add(Key.LSystem, "Left Cmd");
|
||||
_keyNiceNameMap.Add(Key.RSystem, "Right Cmd");
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
_keyNiceNameMap.Add(Key.LSystem, "Left Win");
|
||||
_keyNiceNameMap.Add(Key.RSystem, "Right Win");
|
||||
}
|
||||
else
|
||||
{
|
||||
_keyNiceNameMap.Add(Key.LSystem, "Left Meta");
|
||||
_keyNiceNameMap.Add(Key.RSystem, "Right Meta");
|
||||
}
|
||||
return loc.GetString("input-key-unknown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,12 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using YamlDotNet.Core;
|
||||
@@ -36,7 +37,7 @@ namespace Robust.Client.Input
|
||||
|
||||
[ViewVariables] public bool Enabled { get; set; } = true;
|
||||
|
||||
[ViewVariables] public virtual Vector2 MouseScreenPosition => Vector2.Zero;
|
||||
[ViewVariables] public virtual ScreenCoordinates MouseScreenPosition => default;
|
||||
|
||||
[Dependency] private readonly IResourceManager _resourceMan = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
@@ -145,13 +146,13 @@ namespace Robust.Client.Input
|
||||
.Where(p => _bindingsByFunction[p].Count == 0)
|
||||
.ToArray();
|
||||
|
||||
mapping.AddNode("version", new ValueDataNode("1"));
|
||||
mapping.AddNode("binds", serializationManager.WriteValue(modifiedBindings));
|
||||
mapping.AddNode("leaveEmpty", serializationManager.WriteValue(leaveEmpty));
|
||||
mapping.Add("version", new ValueDataNode("1"));
|
||||
mapping.Add("binds", serializationManager.WriteValue(modifiedBindings));
|
||||
mapping.Add("leaveEmpty", serializationManager.WriteValue(leaveEmpty));
|
||||
|
||||
var path = new ResourcePath(KeybindsPath);
|
||||
using var writer = new StreamWriter(_resourceMan.UserData.Create(path));
|
||||
var stream = new YamlStream {new(mapping.ToMappingNode())};
|
||||
var stream = new YamlStream {new(mapping.ToYaml())};
|
||||
stream.Save(new YamlMappingFix(new Emitter(writer)), false);
|
||||
}
|
||||
|
||||
@@ -333,6 +334,7 @@ namespace Robust.Client.Input
|
||||
{
|
||||
// christ this crap *is* re-entrant thanks to PlacementManager and
|
||||
// I honestly have no idea what the best solution here is.
|
||||
// note from the future: context switches won't cause re-entrancy anymore because InputContextContainer defers context switches
|
||||
DebugTools.Assert(!_currentlyFindingViewport, "Re-entrant key events??");
|
||||
|
||||
try
|
||||
@@ -340,11 +342,13 @@ namespace Robust.Client.Input
|
||||
// This is terrible but anyways.
|
||||
// This flag keeps track of "did a viewport fire the key up for us" so we know we don't do it again.
|
||||
_currentlyFindingViewport = true;
|
||||
// And this stops context switches from causing crashes
|
||||
Contexts.DeferringEnabled = true;
|
||||
|
||||
binding.State = state;
|
||||
|
||||
var eventArgs = new BoundKeyEventArgs(binding.Function, binding.State,
|
||||
new ScreenCoordinates(MouseScreenPosition), binding.CanFocus);
|
||||
MouseScreenPosition, binding.CanFocus);
|
||||
|
||||
// UI returns true here into blockPass if it wants to prevent us from giving input events
|
||||
// to the viewport, but doesn't want it hard-handled so we keep processing possible key actions.
|
||||
@@ -360,12 +364,14 @@ namespace Robust.Client.Input
|
||||
finally
|
||||
{
|
||||
_currentlyFindingViewport = false;
|
||||
Contexts.DeferringEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ViewportKeyEvent(Control? viewport, BoundKeyEventArgs eventArgs)
|
||||
{
|
||||
_currentlyFindingViewport = false;
|
||||
Contexts.DeferringEnabled = false;
|
||||
|
||||
var cmd = GetInputCommand(eventArgs.Function);
|
||||
// TODO: Allow input commands to still get forwarded to server if necessary.
|
||||
@@ -453,7 +459,7 @@ namespace Robust.Client.Input
|
||||
var robustMapping = mapping.ToDataNode() as MappingDataNode;
|
||||
if (robustMapping == null) throw new InvalidOperationException();
|
||||
|
||||
if (robustMapping.TryGetNode("binds", out var BaseKeyRegsNode))
|
||||
if (robustMapping.TryGet("binds", out var BaseKeyRegsNode))
|
||||
{
|
||||
var baseKeyRegs = serializationManager.ReadValueOrThrow<KeyBindingRegistration[]>(BaseKeyRegsNode);
|
||||
|
||||
@@ -482,7 +488,7 @@ namespace Robust.Client.Input
|
||||
}
|
||||
}
|
||||
|
||||
if (userData && robustMapping.TryGetNode("leaveEmpty", out var node))
|
||||
if (userData && robustMapping.TryGet("leaveEmpty", out var node))
|
||||
{
|
||||
var leaveEmpty = serializationManager.ReadValueOrThrow<BoundKeyFunction[]>(node);
|
||||
|
||||
|
||||
@@ -56,8 +56,7 @@ namespace Robust.Client.Map
|
||||
continue;
|
||||
}
|
||||
|
||||
CreateGrid(gridData![gridId].Coordinates.MapId, gridId, creationDatum.ChunkSize,
|
||||
creationDatum.SnapSize);
|
||||
CreateGrid(gridData![gridId].Coordinates.MapId, gridId, creationDatum.ChunkSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user