Skip to content

Commit

Permalink
Merge branch 'master' into compiler-optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
Reagan Fischer authored Feb 9, 2024
2 parents 5459cba + 5666fa3 commit 2d620f9
Show file tree
Hide file tree
Showing 50 changed files with 1,129 additions and 464 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@ jobs:
dirtyLabel: "Merge Conflict"
repoToken: "${{ secrets.GITHUB_TOKEN }}"
commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request."
- name: Apply Size Label
uses: pascalgn/[email protected]
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

2 changes: 1 addition & 1 deletion .github/workflows/test-tgs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ env:
OD_DOTNET_VERSION: 8
TGS_DOTNET_VERSION: 8
TGS_REFERENCE: dev
TGS_TEST_GITHUB_TOKEN: ${{ secrets.TGS_TEST_GITHUB_TOKEN }}
TGS_TEST_GITHUB_TOKEN: ${{ github.token }}

jobs:
build:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/mob/proc/test()
return

/proc/RunTest()
/proc/test_verb_duplicate()
var/mob/m = new
m.verbs += /mob/proc/test
m.verbs += /mob/proc/test
Expand Down
1 change: 1 addition & 0 deletions Content.IntegrationTests/DMProject/code.dm
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@
test_block()
test_color_matrix()
test_range()
test_verb_duplicate()
world.log << "IntegrationTests successful, /world/New() exiting..."
1 change: 1 addition & 0 deletions Content.IntegrationTests/DMProject/environment.dme
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
#include "Tests/block.dm"
#include "Tests/color_matrix.dm"
#include "Tests/range.dm"
#include "Tests/verb_duplicate.dm"
#include "map.dmm"
#include "interface.dmf"
6 changes: 6 additions & 0 deletions Content.Tests/DMProject/Tests/Builtins/isnull.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/proc/RunTest()
ASSERT(isnull(null))
var/obj/O = new()
ASSERT(!isnull(O))
del(O)
ASSERT(isnull(O))
10 changes: 10 additions & 0 deletions Content.Tests/DMProject/Tests/Procs/doublebracketlistarg.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/proc/have_fun(ways_to_have_fun[] = list("dancing", "jumping around", "sightseeing"))
return ways_to_have_fun[2]

/proc/emptylistproc(emptylist[] = null)
return emptylist

/proc/RunTest()
ASSERT(have_fun() == "jumping around")
ASSERT(have_fun(list("eating cake", "spinning")) == "spinning")
ASSERT(isnull(emptylistproc()))
3 changes: 2 additions & 1 deletion Content.Tests/DMProject/Tests/Text/num2text.dm
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@

// Zero/Negative MinDigits
ASSERT(num2text(1, 0, 10) == "1")
ASSERT(num2text(1, -1, 10) == "1")
ASSERT(num2text(1, -1, 10) == "1")
ASSERT(num2text(0, 0, 16) == "0")
15 changes: 11 additions & 4 deletions DMCompiler/Compiler/DM/DMAST.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1324,10 +1324,17 @@ public override void Visit(DMASTVisitor visitor) {
}

public bool AllValuesConstant() {
return Values.All(value => value is {
Key: DMASTExpressionConstant,
Value: DMASTExpressionConstant
});
return Values.All(
value => (value is {
Key: DMASTExpressionConstant,
Value: DMASTExpressionConstant
})
||
(value is {
Key: DMASTExpressionConstant,
Value: DMASTList valueList
} && valueList.AllValuesConstant())
);
}
}

Expand Down
5 changes: 5 additions & 0 deletions DMCompiler/Compiler/DM/DMParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1654,6 +1654,11 @@ public List<DMASTDefinitionParameter> DefinitionParameters(out bool wasIndetermi
DMASTExpression? value = PathArray(ref path.Path);
DMASTExpression? possibleValues = null;

if (Check(TokenType.DM_DoubleSquareBracketEquals)) {
Whitespace();
value = Expression();
}

if (Check(TokenType.DM_Equals)) {
Whitespace();
value = Expression();
Expand Down
179 changes: 179 additions & 0 deletions OpenDreamClient/ClientVerbSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
using System.Threading.Tasks;
using OpenDreamClient.Interface;
using OpenDreamClient.Rendering;
using OpenDreamShared.Dream;
using OpenDreamShared.Rendering;
using Robust.Client.Player;
using Robust.Shared.Asynchronous;
using Robust.Shared.Timing;

namespace OpenDreamClient;

public sealed class ClientVerbSystem : VerbSystem {
[Dependency] private readonly IDreamInterfaceManager _interfaceManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
[Dependency] private readonly ITimerManager _timerManager = default!;

private EntityQuery<DMISpriteComponent> _spriteQuery;
private EntityQuery<DreamMobSightComponent> _sightQuery;

private readonly Dictionary<int, VerbInfo> _verbs = new();
private List<int>? _clientVerbs;

public override void Initialize() {
_spriteQuery = _entityManager.GetEntityQuery<DMISpriteComponent>();
_sightQuery = _entityManager.GetEntityQuery<DreamMobSightComponent>();

_playerManager.LocalPlayerAttached += OnLocalPlayerAttached;

SubscribeNetworkEvent<AllVerbsEvent>(OnAllVerbsEvent);
SubscribeNetworkEvent<RegisterVerbEvent>(OnRegisterVerbEvent);
SubscribeNetworkEvent<UpdateClientVerbsEvent>(OnUpdateClientVerbsEvent);
}

public override void Shutdown() {
_verbs.Clear();
_clientVerbs = null;
}

/// <summary>
/// Prompt the user for the arguments to a verb, then ask the server to execute it
/// </summary>
/// <param name="src">The target of the verb</param>
/// <param name="verbId">ID of the verb to execute</param>
public async void ExecuteVerb(ClientObjectReference src, int verbId) {
var verbInfo = _verbs[verbId];

RaiseNetworkEvent(new ExecuteVerbEvent(src, verbId, await PromptVerbArguments(verbInfo)));
}

/// <summary>
/// Ask the server to execute a verb with the given arguments
/// </summary>
/// <param name="src">The target of the verb</param>
/// <param name="verbId">ID of the verb to execute</param>
/// <param name="arguments">Arguments to the verb</param>
/// <remarks>The server will not execute the verb if the arguments are invalid</remarks> // TODO: I think the server actually just errors, fix that
public void ExecuteVerb(ClientObjectReference src, int verbId, object?[] arguments) {
RaiseNetworkEvent(new ExecuteVerbEvent(src, verbId, arguments));
}

public IEnumerable<VerbInfo> GetAllVerbs() {
return _verbs.Values;
}

/// <summary>
/// Find all the verbs the client is currently capable of executing
/// </summary>
/// <param name="ignoreHiddenAttr">Whether to ignore "set hidden = TRUE"</param>
/// <returns>The ID, target, and information of every executable verb</returns>
public IEnumerable<(int Id, ClientObjectReference Src, VerbInfo VerbInfo)> GetExecutableVerbs(bool ignoreHiddenAttr = false) {
DMISpriteComponent? playerSprite = null;
sbyte? seeInvisibility = null;
if (_playerManager.LocalEntity != null) {
playerSprite = _spriteQuery.GetComponent(_playerManager.LocalEntity.Value);
seeInvisibility = _sightQuery.GetComponent(_playerManager.LocalEntity.Value).SeeInvisibility;
}

// First, the verbs attached to our client
if (_clientVerbs != null) {
foreach (var verbId in _clientVerbs) {
if (!_verbs.TryGetValue(verbId, out var verb))
continue;
if (verb.IsHidden(ignoreHiddenAttr, seeInvisibility ?? 0))
continue; // TODO: How do invisible client verbs work when you don't have a mob?

yield return (verbId, ClientObjectReference.Client, verb);
}
}

// Then, the verbs attached to our mob
if (playerSprite?.Icon.Appearance is { } playerAppearance) {
var playerNetEntity = _entityManager.GetNetEntity(_playerManager.LocalEntity);

if (playerNetEntity != null) {
foreach (var verbId in playerAppearance.Verbs) {
if (!_verbs.TryGetValue(verbId, out var verb))
continue;
if (verb.IsHidden(ignoreHiddenAttr, seeInvisibility!.Value))
continue;

yield return (verbId, new(playerNetEntity.Value), verb);
}
}
}
}

public bool TryGetVerbInfo(int verbId, out VerbInfo verbInfo) {
return _verbs.TryGetValue(verbId, out verbInfo);
}

/// <summary>
/// Look for a verb with the given command-name that the client can execute
/// </summary>
/// <param name="commandName">Command-name to look for</param>
/// <returns>The ID, target, and verb information if a verb was found</returns>
public (int Id, ClientObjectReference Src, VerbInfo VerbInfo)? FindVerbWithCommandName(string commandName) {
foreach (var verb in GetExecutableVerbs(true)) {
if (verb.VerbInfo.GetCommandName() == commandName)
return verb;
}

return null;
}

/// <summary>
/// Open prompt windows for the user to enter the arguments to a verb
/// </summary>
/// <param name="verbInfo">The verb to get arguments for</param>
/// <returns>The values the user gives</returns>
private async Task<object?[]> PromptVerbArguments(VerbInfo verbInfo) {
var argumentCount = verbInfo.Arguments.Length;
var arguments = (argumentCount > 0) ? new object?[argumentCount] : Array.Empty<object?>();

for (int i = 0; i < argumentCount; i++) {
var arg = verbInfo.Arguments[i];
var tcs = new TaskCompletionSource<object?>();

_taskManager.RunOnMainThread(() => {
_interfaceManager.Prompt(arg.Types, verbInfo.Name, arg.Name, string.Empty, (_, value) => {
tcs.SetResult(value);
});
});

arguments[i] = await tcs.Task; // Wait for this prompt to finish before moving on to the next
}

return arguments;
}

private void OnAllVerbsEvent(AllVerbsEvent e) {
_verbs.EnsureCapacity(e.Verbs.Count);

for (int i = 0; i < e.Verbs.Count; i++) {
var verb = e.Verbs[i];

_verbs.Add(i, verb);
}

_interfaceManager.DefaultInfo?.RefreshVerbs(this);
}

private void OnRegisterVerbEvent(RegisterVerbEvent e) {
_verbs.Add(e.VerbId, e.VerbInfo);
}

private void OnUpdateClientVerbsEvent(UpdateClientVerbsEvent e) {
_clientVerbs = e.VerbIds;
_interfaceManager.DefaultInfo?.RefreshVerbs(this);
}

private void OnLocalPlayerAttached(EntityUid obj) {
// Our mob changed, update our verb panels
// A little hacky, but also wait half a second for verb information about our mob to arrive
// TODO: Remove this timer
_timerManager.AddTimer(new Timer(500, false, () => _interfaceManager.DefaultInfo?.RefreshVerbs(this)));
}
}
2 changes: 1 addition & 1 deletion OpenDreamClient/Input/ContextMenu/ContextMenuItem.xaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<PanelContainer xmlns="https://spacestation14.io" MouseFilter="Stop">
<BoxContainer Orientation="Horizontal">
<TextureRect Name="Icon" CanShrink="True" Stretch="KeepAspect" SetWidth="32" SetHeight="32" />
<TextureRect Name="Icon" CanShrink="True" Stretch="KeepAspect" SetWidth="32" SetHeight="32" Margin="2"/>
<Label Name="NameLabel" />
</BoxContainer>
</PanelContainer>
66 changes: 27 additions & 39 deletions OpenDreamClient/Input/ContextMenu/ContextMenuItem.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,58 +1,46 @@
using OpenDreamClient.Rendering;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;

namespace OpenDreamClient.Input.ContextMenu {
[GenerateTypedNameReferences]
public sealed partial class ContextMenuItem : PanelContainer {
private static readonly StyleBox HoverStyle = new StyleBoxFlat(Color.Gray);
namespace OpenDreamClient.Input.ContextMenu;

private readonly IUserInterfaceManager _uiManager;
[GenerateTypedNameReferences]
internal sealed partial class ContextMenuItem : PanelContainer {
private static readonly StyleBox HoverStyle = new StyleBoxFlat(Color.Gray);

private readonly MetaDataComponent? _entityMetaData;
private VerbMenuPopup? _currentVerbMenu;
public readonly EntityUid Entity;
public readonly MetaDataComponent EntityMetaData;
public readonly DMISpriteComponent? EntitySprite;

public ContextMenuItem(IUserInterfaceManager uiManager, IEntityManager entityManager, EntityUid entity) {
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
private readonly ContextMenuPopup _menu;

_uiManager = uiManager;
public ContextMenuItem(ContextMenuPopup menu, EntityUid entity, MetaDataComponent metadata, DMISpriteComponent sprite) {
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);

NameLabel.Margin = new Thickness(2, 0, 4, 0);
if (entityManager.TryGetComponent(entity, out _entityMetaData)) {
NameLabel.Text = _entityMetaData.EntityName;
}
Entity = entity;
EntityMetaData = metadata;
EntitySprite = sprite;
_menu = menu;

Icon.Margin = new Thickness(2);
if (entityManager.TryGetComponent(entity, out DMISpriteComponent? sprite)) {
Icon.Texture = sprite.Icon.CurrentFrame;
}
NameLabel.Margin = new Thickness(2, 0, 4, 0);
NameLabel.Text = metadata.EntityName;

OnMouseEntered += MouseEntered;
OnMouseExited += MouseExited;
}

private void MouseEntered(GUIMouseHoverEventArgs args) {
PanelOverride = HoverStyle;
Icon.Texture = sprite.Icon.CurrentFrame;
}

_currentVerbMenu = new VerbMenuPopup(_entityMetaData);
_uiManager.ModalRoot.AddChild(_currentVerbMenu);
protected override void MouseEntered() {
base.MouseEntered();

Vector2 desiredSize = _currentVerbMenu.DesiredSize;
_currentVerbMenu.Open(UIBox2.FromDimensions(new Vector2(GlobalPosition.X + Size.X, GlobalPosition.Y), desiredSize));
}
PanelOverride = HoverStyle;
_menu.SetActiveItem(this);
}

private void MouseExited(GUIMouseHoverEventArgs args) {
PanelOverride = null;
protected override void MouseExited() {
base.MouseExited();

if (_currentVerbMenu != null) {
_currentVerbMenu.Close();
_uiManager.ModalRoot.RemoveChild(_currentVerbMenu);
_currentVerbMenu = null;
}
}
PanelOverride = null;
}
}
Loading

0 comments on commit 2d620f9

Please sign in to comment.