From a14ee4af9e17b2655c8d3b4b02f832d44d37d9b8 Mon Sep 17 00:00:00 2001 From: wixoa Date: Sun, 24 Sep 2023 22:47:02 -0400 Subject: [PATCH 01/31] Fix stuck repeating commands (#1468) --- OpenDreamRuntime/Input/DreamCommandSystem.cs | 56 ++++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/OpenDreamRuntime/Input/DreamCommandSystem.cs b/OpenDreamRuntime/Input/DreamCommandSystem.cs index e37207a98d..cd87e8ee0f 100644 --- a/OpenDreamRuntime/Input/DreamCommandSystem.cs +++ b/OpenDreamRuntime/Input/DreamCommandSystem.cs @@ -1,43 +1,43 @@ using OpenDreamShared.Input; using Robust.Server.Player; -namespace OpenDreamRuntime.Input { - sealed class DreamCommandSystem : SharedDreamCommandSystem { - [Dependency] private readonly DreamManager _dreamManager = default!; +namespace OpenDreamRuntime.Input; - private readonly List<(string Command, IPlayerSession session)> _repeatingCommands = new(); +internal sealed class DreamCommandSystem : SharedDreamCommandSystem { + [Dependency] private readonly DreamManager _dreamManager = default!; - public override void Initialize() { - SubscribeNetworkEvent(OnCommandEvent); - SubscribeNetworkEvent(OnRepeatCommandEvent); - SubscribeNetworkEvent(OnStopRepeatCommandEvent); - } + private readonly HashSet<(string Command, IPlayerSession session)> _repeatingCommands = new(); - public void RunRepeatingCommands() { - foreach (var (command, session) in _repeatingCommands) { - RunCommand(command, session); - } - } + public override void Initialize() { + SubscribeNetworkEvent(OnCommandEvent); + SubscribeNetworkEvent(OnRepeatCommandEvent); + SubscribeNetworkEvent(OnStopRepeatCommandEvent); + } - private void OnCommandEvent(CommandEvent e, EntitySessionEventArgs sessionEvent) { - RunCommand(e.Command, (IPlayerSession)sessionEvent.SenderSession); + public void RunRepeatingCommands() { + foreach (var (command, session) in _repeatingCommands) { + RunCommand(command, session); } + } - private void OnRepeatCommandEvent(RepeatCommandEvent e, EntitySessionEventArgs sessionEvent) { - var tuple = (e.Command, (IPlayerSession)sessionEvent.SenderSession); + private void OnCommandEvent(CommandEvent e, EntitySessionEventArgs sessionEvent) { + RunCommand(e.Command, (IPlayerSession)sessionEvent.SenderSession); + } - _repeatingCommands.Add(tuple); - } + private void OnRepeatCommandEvent(RepeatCommandEvent e, EntitySessionEventArgs sessionEvent) { + var tuple = (e.Command, (IPlayerSession)sessionEvent.SenderSession); - private void OnStopRepeatCommandEvent(StopRepeatCommandEvent e, EntitySessionEventArgs sessionEvent) { - var tuple = (e.Command, (IPlayerSession)sessionEvent.SenderSession); + _repeatingCommands.Add(tuple); + } - _repeatingCommands.Remove(tuple); - } + private void OnStopRepeatCommandEvent(StopRepeatCommandEvent e, EntitySessionEventArgs sessionEvent) { + var tuple = (e.Command, (IPlayerSession)sessionEvent.SenderSession); - private void RunCommand(string command, IPlayerSession session) { - var connection = _dreamManager.GetConnectionBySession(session); - connection.HandleCommand(command); - } + _repeatingCommands.Remove(tuple); + } + + private void RunCommand(string command, IPlayerSession session) { + var connection = _dreamManager.GetConnectionBySession(session); + connection.HandleCommand(command); } } From acb8f34542fef255572fad83e2b9c4e5c1249d64 Mon Sep 17 00:00:00 2001 From: Amy <3855802+amylizzle@users.noreply.github.com> Date: Mon, 25 Sep 2023 04:08:50 +0100 Subject: [PATCH 02/31] Fix plane master render target handling (#1455) --- OpenDreamClient/Rendering/DreamViewOverlay.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index 7e4029b864..ce6dd1c04e 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -253,6 +253,15 @@ private void ProcessIconComponents(DreamIcon icon, Vector2 position, EntityUid u renderTargetPlaceholder.Layer = current.Layer; renderTargetPlaceholder.RenderSource = current.RenderTarget; renderTargetPlaceholder.MouseOpacity = current.MouseOpacity; + if((current.AppearanceFlags & AppearanceFlags.PlaneMaster) != 0){ //Plane masters with render targets get special handling + renderTargetPlaceholder.TransformToApply = current.TransformToApply; + renderTargetPlaceholder.ColorToApply = current.ColorToApply; + renderTargetPlaceholder.ColorMatrixToApply = current.ColorMatrixToApply; + renderTargetPlaceholder.AlphaToApply = current.AlphaToApply; + renderTargetPlaceholder.BlendMode = current.BlendMode; + } + renderTargetPlaceholder.AppearanceFlags = current.AppearanceFlags; + current.AppearanceFlags = current.AppearanceFlags & ~AppearanceFlags.PlaneMaster; //only the placeholder should be marked as master result.Add(renderTargetPlaceholder); } From 7ce77dde0783aa091338a686d56db00257ffca25 Mon Sep 17 00:00:00 2001 From: wixoa Date: Sun, 24 Sep 2023 23:11:24 -0400 Subject: [PATCH 03/31] Set default atom names (#1467) --- DMCompiler/DMStandard/Types/Atoms/_Atom.dm | 2 +- OpenDreamRuntime/Objects/DreamObjectTree.cs | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/DMCompiler/DMStandard/Types/Atoms/_Atom.dm b/DMCompiler/DMStandard/Types/Atoms/_Atom.dm index 772b090d71..faee9c15d4 100644 --- a/DMCompiler/DMStandard/Types/Atoms/_Atom.dm +++ b/DMCompiler/DMStandard/Types/Atoms/_Atom.dm @@ -1,7 +1,7 @@ /atom parent_type = /datum - var/name = "atom" + var/name = null var/text = null var/desc = null var/suffix = null as opendream_unimplemented diff --git a/OpenDreamRuntime/Objects/DreamObjectTree.cs b/OpenDreamRuntime/Objects/DreamObjectTree.cs index dc00e5caa4..043b112383 100644 --- a/OpenDreamRuntime/Objects/DreamObjectTree.cs +++ b/OpenDreamRuntime/Objects/DreamObjectTree.cs @@ -342,10 +342,13 @@ private void LoadTypesFromJson(DreamTypeJson[] types) { type.ParentEntry.ChildCount += type.ChildCount + 1; } - //Fifth pass: Set atom's text + //Fifth pass: Set atom's name and text foreach (TreeEntry type in GetAllDescendants(Atom)) { - if (type.ObjectDefinition.Variables["text"].Equals(DreamValue.Null) && type.ObjectDefinition.Variables["name"].TryGetValueAsString(out var name)) { - type.ObjectDefinition.SetVariableDefinition("text", new DreamValue(String.IsNullOrEmpty(name) ? String.Empty : name[..1])); + if (type.ObjectDefinition.Variables["name"].IsNull) + type.ObjectDefinition.Variables["name"] = new(type.Path.LastElement!.Replace("_", " ")); + + if (type.ObjectDefinition.Variables["text"].IsNull && type.ObjectDefinition.Variables["name"].TryGetValueAsString(out var name)) { + type.ObjectDefinition.Variables["text"] = new DreamValue(string.IsNullOrEmpty(name) ? string.Empty : name[..1]); } } } From 16f26ceecefa0ee7e42e34a91e38da1fb020d649 Mon Sep 17 00:00:00 2001 From: Amy <3855802+amylizzle@users.noreply.github.com> Date: Wed, 27 Sep 2023 16:12:53 +0100 Subject: [PATCH 04/31] `is-checked` and `group` for menus (#1462) * Checkbox + group checks * Update OpenDreamClient/Interface/InterfaceMenu.cs Co-authored-by: wixoa --------- Co-authored-by: wixoa --- .../Interface/Descriptors/MenuDescriptors.cs | 8 +++ OpenDreamClient/Interface/InterfaceMenu.cs | 55 +++++++++++++++++-- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/OpenDreamClient/Interface/Descriptors/MenuDescriptors.cs b/OpenDreamClient/Interface/Descriptors/MenuDescriptors.cs index 555b3c638d..c908df301a 100644 --- a/OpenDreamClient/Interface/Descriptors/MenuDescriptors.cs +++ b/OpenDreamClient/Interface/Descriptors/MenuDescriptors.cs @@ -48,6 +48,14 @@ public string? Category { [DataField("can-check")] public bool CanCheck { get; private set; } + [DataField("is-checked")] + public bool IsChecked { get; set; } + + [DataField("group")] + public string? Group { get; private set; } + [DataField("index")] + public int Index { get; private set; } + public MenuElementDescriptor WithCategory(ISerializationManager serialization, string category) { var copy = serialization.CreateCopy(this, notNullableOverride: true); diff --git a/OpenDreamClient/Interface/InterfaceMenu.cs b/OpenDreamClient/Interface/InterfaceMenu.cs index cd98515bad..f1edfcb19e 100644 --- a/OpenDreamClient/Interface/InterfaceMenu.cs +++ b/OpenDreamClient/Interface/InterfaceMenu.cs @@ -23,6 +23,17 @@ public InterfaceMenu(MenuDescriptor descriptor) : base(descriptor) { CreateMenu(); } + public void SetGroupChecked(string group, string id) { + foreach (MenuElement menuElement in MenuElements.Values) { + if (menuElement.ElementDescriptor is not MenuElementDescriptor menuElementDescriptor) + continue; + + if (menuElementDescriptor.Group == group) { + menuElementDescriptor.IsChecked = menuElementDescriptor.Id == id; + } + } + } + public override void AddChild(ElementDescriptor descriptor) { if (descriptor is not MenuElementDescriptor elementDescriptor) throw new ArgumentException($"Attempted to add a {descriptor} to a menu", nameof(descriptor)); @@ -80,7 +91,6 @@ public sealed class MenuElement : InterfaceElement { private MenuElementDescriptor MenuElementDescriptor => (MenuElementDescriptor) ElementDescriptor; public string? Category => MenuElementDescriptor.Category; public string Command => MenuElementDescriptor.Command; - private readonly InterfaceMenu _menu; public MenuElement(MenuElementDescriptor data, InterfaceMenu menu) : base(data) { @@ -106,16 +116,53 @@ public MenuBar.MenuEntry CreateMenuEntry() { if (String.IsNullOrEmpty(text)) return new MenuBar.MenuSeparator(); + if(MenuElementDescriptor.CanCheck) + if(MenuElementDescriptor.IsChecked) + text = text + " ☑"; + MenuBar.MenuButton menuButton = new() { Text = text }; - //result.IsCheckable = MenuElementDescriptor.CanCheck; - if (!String.IsNullOrEmpty(Command)) - menuButton.OnPressed += () => { EntitySystem.Get().RunCommand(Command); }; + + menuButton.OnPressed += () => { + if(MenuElementDescriptor.CanCheck) + if(!String.IsNullOrEmpty(MenuElementDescriptor.Group)) + _menu.SetGroupChecked(MenuElementDescriptor.Group, MenuElementDescriptor.Id); + else + MenuElementDescriptor.IsChecked = !MenuElementDescriptor.IsChecked; + _menu.CreateMenu(); + if(!string.IsNullOrEmpty(MenuElementDescriptor.Command)) + EntitySystem.Get().RunCommand(Command); + }; return menuButton; } + public override bool TryGetProperty(string property, out string value) { + switch (property) { + case "command": + value = Command; + return true; + case "category": + value = Category ?? ""; + return true; + case "can-check": + value = MenuElementDescriptor.CanCheck.ToString(); + return true; + case "is-checked": + value = MenuElementDescriptor.IsChecked.ToString(); + return true; + case "group": + value = MenuElementDescriptor.Group ?? ""; + return true; + case "index": + value = MenuElementDescriptor.Index.ToString(); + return true; + default: + return base.TryGetProperty(property, out value); + } + } + public override void AddChild(ElementDescriptor descriptor) { // Set the child's category to this element // TODO: The "parent" and "category" attributes seem to be treated differently in BYOND; not the same thing. From 3ef630f3b635db3fd0538a369afcc88176389b7a Mon Sep 17 00:00:00 2001 From: Amy <3855802+amylizzle@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:39:07 +0100 Subject: [PATCH 05/31] Implement `/tmp` and `/const` var decorators on object vars for `issaved()` (#1443) * fun * oops, missed those * redundant test * remove useless test * Update OpenDreamShared/Json/DreamObjectJson.cs Co-authored-by: wixoa * move to default * const/type * no readonly * oops * missed a couple * logic better * readability, something bugged * fixed --------- Co-authored-by: amy Co-authored-by: wixoa --- .../DMProject/Broken Tests/Stdlib/issaved.dm | 17 ----------------- .../Special Procs/issaved/issaved_vars_index.dm | 12 ++++++------ DMCompiler/DM/DMObject.cs | 10 ++++++++++ DMCompiler/DM/DMObjectTree.cs | 2 +- DMCompiler/DM/DMVariable.cs | 6 ++++-- DMCompiler/DM/Visitors/DMObjectBuilder.cs | 10 +++++++++- DMCompiler/DMStandard/Types/Client.dm | 2 +- DMCompiler/DMStandard/Types/Datum.dm | 6 +++--- DMCompiler/DMStandard/Types/List.dm | 2 +- OpenDreamRuntime/Objects/DreamObject.cs | 8 ++++++-- .../Objects/DreamObjectDefinition.cs | 10 ++++++++++ OpenDreamRuntime/Objects/DreamObjectTree.cs | 14 ++++++++++++++ OpenDreamRuntime/Procs/DMOpcodeHandlers.cs | 5 +++-- OpenDreamShared/Json/DreamObjectJson.cs | 2 ++ 14 files changed, 70 insertions(+), 36 deletions(-) delete mode 100644 Content.Tests/DMProject/Broken Tests/Stdlib/issaved.dm diff --git a/Content.Tests/DMProject/Broken Tests/Stdlib/issaved.dm b/Content.Tests/DMProject/Broken Tests/Stdlib/issaved.dm deleted file mode 100644 index 073b9306dc..0000000000 --- a/Content.Tests/DMProject/Broken Tests/Stdlib/issaved.dm +++ /dev/null @@ -1,17 +0,0 @@ - -// TODO: This test needs further cleanup/validation but I cba and we need more issaved() tests - -//# issue 684 - -/obj - var/V - var/const/C - - proc/log_vars() - for(var/vname in vars) - world.log << (issaved(vars[vname])) - -/proc/RunTest() - var/obj/o = new - o.log_vars() - ASSERT(FALSE) // To ensure this test fails until it's been revisited (in case we add CI to check broken tests) diff --git a/Content.Tests/DMProject/Tests/Special Procs/issaved/issaved_vars_index.dm b/Content.Tests/DMProject/Tests/Special Procs/issaved/issaved_vars_index.dm index 293fcc7d2b..f77f95dc52 100644 --- a/Content.Tests/DMProject/Tests/Special Procs/issaved/issaved_vars_index.dm +++ b/Content.Tests/DMProject/Tests/Special Procs/issaved/issaved_vars_index.dm @@ -1,23 +1,23 @@ -// !issaved(B) commented out because of TODO in DMOpcodeHandlers.IsSaved /obj/o var/A - //var/tmp/B + var/tmp/B /obj/o/proc/IsSavedSrcVars() ASSERT(issaved(A)) - //ASSERT(!issaved(B)) + ASSERT(!issaved(B)) ASSERT(issaved(vars["A"])) - //ASSERT(!issaved(vars["B"])) + ASSERT(!issaved(vars["B"])) /proc/RunTest() var/obj/o/test = new + ASSERT(!issaved(test.type)) ASSERT(issaved(test.A)) - //ASSERT(!issaved(test.B)) + ASSERT(!issaved(test.B)) // Note that this doesn't work on most lists and will instead return false ASSERT(issaved(test.vars["A"])) - //ASSERT(!issaved(test.vars["B"])) + ASSERT(!issaved(test.vars["B"])) /* var/expected = prob(50) diff --git a/DMCompiler/DM/DMObject.cs b/DMCompiler/DM/DMObject.cs index 1c488a92b4..de02e80c06 100644 --- a/DMCompiler/DM/DMObject.cs +++ b/DMCompiler/DM/DMObject.cs @@ -21,6 +21,8 @@ internal sealed class DMObject { public Dictionary VariableOverrides = new(); public Dictionary GlobalVariables = new(); /// A list of var and verb initializations implicitly done before the user's New() is called. + public HashSet ConstVariables = new(); + public HashSet TmpVariables = new(); public List InitializationProcExpressions = new(); public int? InitializationProc; @@ -169,6 +171,14 @@ public DreamTypeJson CreateJsonRepresentation() { typeJson.GlobalVariables = GlobalVariables; } + if (ConstVariables.Count > 0) { + typeJson.ConstVariables = ConstVariables; + } + + if (TmpVariables.Count > 0) { + typeJson.TmpVariables = TmpVariables; + } + if (InitializationProc != null) { typeJson.InitProc = InitializationProc; } diff --git a/DMCompiler/DM/DMObjectTree.cs b/DMCompiler/DM/DMObjectTree.cs index daffd397c0..6773b4f58d 100644 --- a/DMCompiler/DM/DMObjectTree.cs +++ b/DMCompiler/DM/DMObjectTree.cs @@ -173,7 +173,7 @@ public static bool TryGetTypeId(DreamPath path, out int typeId) { public static int CreateGlobal(out DMVariable global, DreamPath? type, string name, bool isConst, DMValueType valType = DMValueType.Anything) { int id = Globals.Count; - global = new DMVariable(type, name, true, isConst, valType); + global = new DMVariable(type, name, true, isConst, false, valType); Globals.Add(global); return id; } diff --git a/DMCompiler/DM/DMVariable.cs b/DMCompiler/DM/DMVariable.cs index 86f63e91c9..6f2efaea20 100644 --- a/DMCompiler/DM/DMVariable.cs +++ b/DMCompiler/DM/DMVariable.cs @@ -10,14 +10,16 @@ sealed class DMVariable { /// NOTE: This DMVariable may be forced constant through opendream_compiletimereadonly. This only marks that the variable has the DM quality of /const/ness. /// public bool IsConst; + public bool IsTmp; public DMExpression Value; public DMValueType ValType; - public DMVariable(DreamPath? type, string name, bool isGlobal, bool isConst, DMValueType valType = DMValueType.Anything) { + public DMVariable(DreamPath? type, string name, bool isGlobal, bool isConst, bool isTmp, DMValueType valType = DMValueType.Anything) { Type = type; Name = name; IsGlobal = isGlobal; IsConst = isConst; + IsTmp = isTmp; Value = null; ValType = valType; } @@ -33,7 +35,7 @@ public DMVariable WriteToValue(Expressions.Constant value) { return this; } - DMVariable clone = new DMVariable(Type, Name, IsGlobal, IsConst, ValType); + DMVariable clone = new DMVariable(Type, Name, IsGlobal, IsConst, IsTmp, ValType); clone.Value = value; return clone; } diff --git a/DMCompiler/DM/Visitors/DMObjectBuilder.cs b/DMCompiler/DM/Visitors/DMObjectBuilder.cs index 1b766b1719..9d62e01b4d 100644 --- a/DMCompiler/DM/Visitors/DMObjectBuilder.cs +++ b/DMCompiler/DM/Visitors/DMObjectBuilder.cs @@ -274,8 +274,16 @@ private static void ProcessVarDefinition(DMObject? varObject, DMASTObjectVarDefi if (varDefinition.IsStatic) { variable = varObject.CreateGlobalVariable(varDefinition.Type, varDefinition.Name, varDefinition.IsConst, varDefinition.ValType); } else { - variable = new DMVariable(varDefinition.Type, varDefinition.Name, false, varDefinition.IsConst,varDefinition.ValType); + variable = new DMVariable(varDefinition.Type, varDefinition.Name, false, varDefinition.IsConst, varDefinition.IsTmp, varDefinition.ValType); varObject.Variables[variable.Name] = variable; + if(varDefinition.IsConst){ + varObject.ConstVariables ??= new HashSet(); + varObject.ConstVariables.Add(varDefinition.Name); + } + if(varDefinition.IsTmp){ + varObject.TmpVariables ??= new HashSet(); + varObject.TmpVariables.Add(varDefinition.Name); + } } } diff --git a/DMCompiler/DMStandard/Types/Client.dm b/DMCompiler/DMStandard/Types/Client.dm index 4ec61aeeab..4f6f895580 100644 --- a/DMCompiler/DMStandard/Types/Client.dm +++ b/DMCompiler/DMStandard/Types/Client.dm @@ -9,7 +9,7 @@ var/default_verb_category = "Commands" var/tag - var/type = /client + var/const/type = /client var/mob/mob var/atom/eye diff --git a/DMCompiler/DMStandard/Types/Datum.dm b/DMCompiler/DMStandard/Types/Datum.dm index 66e47ef3ba..ce2d1c7b01 100644 --- a/DMCompiler/DMStandard/Types/Datum.dm +++ b/DMCompiler/DMStandard/Types/Datum.dm @@ -1,8 +1,8 @@ /datum - var/type - var/parent_type + var/const/type + var/tmp/parent_type - var/list/vars + var/const/list/vars var/tag diff --git a/DMCompiler/DMStandard/Types/List.dm b/DMCompiler/DMStandard/Types/List.dm index ca0faa0822..ce16246e56 100644 --- a/DMCompiler/DMStandard/Types/List.dm +++ b/DMCompiler/DMStandard/Types/List.dm @@ -1,6 +1,6 @@ /list var/len - var/type = /list + var/const/type = /list proc/New(Size) diff --git a/OpenDreamRuntime/Objects/DreamObject.cs b/OpenDreamRuntime/Objects/DreamObject.cs index 3d054eb781..373b491025 100644 --- a/OpenDreamRuntime/Objects/DreamObject.cs +++ b/OpenDreamRuntime/Objects/DreamObject.cs @@ -121,8 +121,10 @@ public virtual DreamValue Initial(string name) { } public virtual bool IsSaved(string name) { - //TODO: Add support for var/const/ and var/tmp/ once those are properly in - return ObjectDefinition.Variables.ContainsKey(name) && !ObjectDefinition.GlobalVariables.ContainsKey(name); + return ObjectDefinition.Variables.ContainsKey(name) + && !ObjectDefinition.GlobalVariables.ContainsKey(name) + && !(ObjectDefinition.ConstVariables is not null && ObjectDefinition.ConstVariables.Contains(name)) + && !(ObjectDefinition.TmpVariables is not null && ObjectDefinition.TmpVariables.Contains(name)); } public bool HasVariable(string name) { @@ -182,6 +184,8 @@ protected virtual void SetVar(string varName, DreamValue value) { Tag = newTag; break; default: + if (ObjectDefinition.ConstVariables is not null && ObjectDefinition.ConstVariables.Contains(varName)) + throw new Exception($"Cannot set const var \"{varName}\" on {ObjectDefinition.Type}"); if (!ObjectDefinition.Variables.ContainsKey(varName)) throw new Exception($"Cannot set var \"{varName}\" on {ObjectDefinition.Type}"); diff --git a/OpenDreamRuntime/Objects/DreamObjectDefinition.cs b/OpenDreamRuntime/Objects/DreamObjectDefinition.cs index 6aad54c8c6..92e24a8686 100644 --- a/OpenDreamRuntime/Objects/DreamObjectDefinition.cs +++ b/OpenDreamRuntime/Objects/DreamObjectDefinition.cs @@ -48,6 +48,10 @@ public bool NoConstructors { public readonly Dictionary Variables = new(); // Maps /static variables from name to their index in the global variable table. public readonly Dictionary GlobalVariables = new(); + // Contains hashes of variables that are tagged /const. + public HashSet? ConstVariables = null; + // Contains hashes of variables that are tagged /tmp. + public HashSet? TmpVariables = null; public DreamObjectDefinition(DreamObjectDefinition copyFrom) { DreamManager = copyFrom.DreamManager; @@ -68,6 +72,8 @@ public DreamObjectDefinition(DreamObjectDefinition copyFrom) { Variables = new Dictionary(copyFrom.Variables); GlobalVariables = new Dictionary(copyFrom.GlobalVariables); + ConstVariables = copyFrom.ConstVariables is not null ? new HashSet(copyFrom.ConstVariables) : null; + TmpVariables = copyFrom.TmpVariables is not null ? new HashSet(copyFrom.TmpVariables) : null; Procs = new Dictionary(copyFrom.Procs); OverridingProcs = new Dictionary(copyFrom.OverridingProcs); if (copyFrom.Verbs != null) @@ -97,6 +103,10 @@ public DreamObjectDefinition(DreamManager dreamManager, DreamObjectTree objectTr Verbs = new List(Parent.Verbs); if (Parent != ObjectTree.Root.ObjectDefinition) // Don't include root-level globals GlobalVariables = new Dictionary(Parent.GlobalVariables); + if (Parent.ConstVariables != null) + ConstVariables = new HashSet(Parent.ConstVariables); + if (Parent.TmpVariables != null) + TmpVariables = new HashSet(Parent.TmpVariables); } } diff --git a/OpenDreamRuntime/Objects/DreamObjectTree.cs b/OpenDreamRuntime/Objects/DreamObjectTree.cs index 043b112383..49cf208e69 100644 --- a/OpenDreamRuntime/Objects/DreamObjectTree.cs +++ b/OpenDreamRuntime/Objects/DreamObjectTree.cs @@ -367,6 +367,20 @@ private void LoadVariablesFromJson(DreamObjectDefinition objectDefinition, Dream objectDefinition.GlobalVariables.Add(jsonGlobalVariable.Key, jsonGlobalVariable.Value); } } + + if (jsonObject.ConstVariables != null) { + objectDefinition.ConstVariables ??= new(); + foreach (string jsonConstVariable in jsonObject.ConstVariables) { + objectDefinition.ConstVariables.Add(jsonConstVariable); + } + } + + if(jsonObject.TmpVariables != null) { + objectDefinition.TmpVariables ??= new(); + foreach (string jsonTmpVariable in jsonObject.TmpVariables) { + objectDefinition.TmpVariables.Add(jsonTmpVariable); + } + } } public DreamProc LoadProcJson(int id, DreamTypeJson[] types, ProcDefinitionJson procDefinition) { diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index 0043d5c807..e35bba408e 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -2017,8 +2017,9 @@ public static ProcStatus IsSaved(DMProcState state) { throw new Exception($"Invalid owner for issaved() call {owner}"); } - //TODO: Add support for var/const/ and var/tmp/ once those are properly in - if (objectDefinition.GlobalVariables.ContainsKey(property)) { + if (objectDefinition.GlobalVariables.ContainsKey(property) + || (objectDefinition.ConstVariables is not null && objectDefinition.ConstVariables.Contains(property)) + || (objectDefinition.TmpVariables is not null && objectDefinition.TmpVariables.Contains(property))) { state.Push(new DreamValue(0)); } else { state.Push(new DreamValue(1)); diff --git a/OpenDreamShared/Json/DreamObjectJson.cs b/OpenDreamShared/Json/DreamObjectJson.cs index b34dfb057a..bc5421823f 100644 --- a/OpenDreamShared/Json/DreamObjectJson.cs +++ b/OpenDreamShared/Json/DreamObjectJson.cs @@ -20,6 +20,8 @@ public sealed class DreamTypeJson { public List Verbs { get; set; } public Dictionary Variables { get; set; } public Dictionary GlobalVariables { get; set; } + public HashSet? ConstVariables { get; set; } + public HashSet? TmpVariables { get; set; } } public sealed class GlobalListJson { From 8776a9fa9f6f328d54061e205f25b6b32ce3d7df Mon Sep 17 00:00:00 2001 From: wixoa Date: Thu, 28 Sep 2023 12:30:14 -0400 Subject: [PATCH 06/31] Add support for `zoom-mode` on map controls (#1473) --- OpenDreamClient/Interface/Controls/ControlMap.cs | 15 +++++++++++++++ .../Interface/Descriptors/ControlDescriptors.cs | 2 ++ 2 files changed, 17 insertions(+) diff --git a/OpenDreamClient/Interface/Controls/ControlMap.cs b/OpenDreamClient/Interface/Controls/ControlMap.cs index c163b1d364..4d9ba1d91a 100644 --- a/OpenDreamClient/Interface/Controls/ControlMap.cs +++ b/OpenDreamClient/Interface/Controls/ControlMap.cs @@ -16,6 +16,21 @@ public sealed class ControlMap : InterfaceControl { public ControlMap(ControlDescriptor controlDescriptor, ControlWindow window) : base(controlDescriptor, window) { } + protected override void UpdateElementDescriptor() { + base.UpdateElementDescriptor(); + + ControlDescriptorMap mapDescriptor = (ControlDescriptorMap)ElementDescriptor; + + Viewport.StretchMode = mapDescriptor.ZoomMode switch { + "blur" => ScalingViewportStretchMode.Bilinear, + "distort" => ScalingViewportStretchMode.Nearest, + + // TODO: "tries to keep the look of individual pixels, + // but will adjust to non-integer zooms (like 1.1x) by blending neighboring pixels" + "normal" or _ => ScalingViewportStretchMode.Nearest + }; + } + public void UpdateViewRange(ViewRange view) { Viewport.ViewportSize = (Math.Max(view.Width, 1) * 32, Math.Max(view.Height, 1) * 32); } diff --git a/OpenDreamClient/Interface/Descriptors/ControlDescriptors.cs b/OpenDreamClient/Interface/Descriptors/ControlDescriptors.cs index 748150c46d..147aaa40ea 100644 --- a/OpenDreamClient/Interface/Descriptors/ControlDescriptors.cs +++ b/OpenDreamClient/Interface/Descriptors/ControlDescriptors.cs @@ -138,6 +138,8 @@ public sealed partial class ControlDescriptorInfo : ControlDescriptor { } public sealed partial class ControlDescriptorMap : ControlDescriptor { + [DataField("zoom-mode")] + public string ZoomMode = "normal"; } public sealed partial class ControlDescriptorBrowser : ControlDescriptor { From 3d3a5d463cbe75780a9f0c3f067d18aaa49c04a7 Mon Sep 17 00:00:00 2001 From: Amy <3855802+amylizzle@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:10:01 +0100 Subject: [PATCH 07/31] clone child elements of windows (#1448) Co-authored-by: amy --- OpenDreamClient/Interface/Descriptors/ControlDescriptors.cs | 2 ++ OpenDreamClient/Interface/DreamInterfaceManager.cs | 3 +++ 2 files changed, 5 insertions(+) diff --git a/OpenDreamClient/Interface/Descriptors/ControlDescriptors.cs b/OpenDreamClient/Interface/Descriptors/ControlDescriptors.cs index 147aaa40ea..98ee7ada35 100644 --- a/OpenDreamClient/Interface/Descriptors/ControlDescriptors.cs +++ b/OpenDreamClient/Interface/Descriptors/ControlDescriptors.cs @@ -97,6 +97,8 @@ public override ElementDescriptor CreateCopy(ISerializationManager serialization var copy = serializationManager.CreateCopy(this, notNullableOverride: true); copy._id = id; + foreach(var child in this.ControlDescriptors) + copy.ControlDescriptors.Add(serializationManager.CreateCopy(child, notNullableOverride: false)); return copy; } diff --git a/OpenDreamClient/Interface/DreamInterfaceManager.cs b/OpenDreamClient/Interface/DreamInterfaceManager.cs index ec1cfe9ec8..444ed0e636 100644 --- a/OpenDreamClient/Interface/DreamInterfaceManager.cs +++ b/OpenDreamClient/Interface/DreamInterfaceManager.cs @@ -605,6 +605,9 @@ public void WinClone(string controlId, string cloneId) { } LoadDescriptor(elementDescriptor); + if(elementDescriptor is WindowDescriptor && Windows.TryGetValue(cloneId, out var window)){ + window.CreateChildControls(); + } } private void LoadInterface(InterfaceDescriptor descriptor) { From 5496c1f0c8bc6a1d7db6c1e0953cf8efa9fce965 Mon Sep 17 00:00:00 2001 From: wixoa Date: Mon, 2 Oct 2023 12:15:08 -0400 Subject: [PATCH 08/31] Add support for `#define FILE_DIR` (#1474) * Add support for `#define FILE_DIR` * Update DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs --- .../Compiler/DMPreprocessor/DMPreprocessor.cs | 28 ++++++- DMCompiler/DM/Expressions/Constant.cs | 26 ++++--- DMCompiler/DMCompiler.cs | 75 +++++++++---------- 3 files changed, 79 insertions(+), 50 deletions(-) diff --git a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs index 49398833ba..efb59a9a4d 100644 --- a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs +++ b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs @@ -299,7 +299,30 @@ private void HandleDefineDirective(Token defineToken) { GetLineOfTokens(); // consume what's on this line and leave return; } - if(defineIdentifier.Text == "defined") { + + // #define FILE_DIR is a little special + // Every define will add to a list of directories to check for resource files + if (defineIdentifier.Text == "FILE_DIR") { + Token dirToken = GetNextToken(true); + string? dirTokenValue = dirToken.Type switch { + TokenType.DM_Preproc_ConstantString => (string?)dirToken.Value, + TokenType.DM_Preproc_Punctuator_Period => ".", + _ => null + }; + + if (dirTokenValue is null) { + DMCompiler.Emit(WarningCode.BadDirective, dirToken.Location, $"\"{dirToken.Text}\" is not a valid directory"); + return; + } + + DMPreprocessorLexer currentLexer = _lexerStack.Peek(); + string dir = Path.Combine(currentLexer.IncludeDirectory, dirTokenValue); + DMCompiler.AddResourceDirectory(dir); + + // In BYOND it goes on to set the FILE_DIR macro's value to the added directory + // I don't see any reason to do that + return; + } else if (defineIdentifier.Text == "defined") { DMCompiler.Emit(WarningCode.SoftReservedKeyword, defineIdentifier.Location, "Reserved keyword 'defined' cannot be used as macro name"); } @@ -310,11 +333,10 @@ private void HandleDefineDirective(Token defineToken) { if (macroToken.Type == TokenType.DM_Preproc_Punctuator_LeftParenthesis) { // We're a macro function! parameters = new List(1); //Read in the parameters - Token parameterToken; bool canConsumeComma = false; bool foundVariadic = false; while(true) { - parameterToken = GetNextToken(true); + var parameterToken = GetNextToken(true); switch(parameterToken.Type) { case TokenType.DM_Preproc_Identifier: canConsumeComma = true; diff --git a/DMCompiler/DM/Expressions/Constant.cs b/DMCompiler/DM/Expressions/Constant.cs index 7fa01a60f3..1894e2145f 100644 --- a/DMCompiler/DM/Expressions/Constant.cs +++ b/DMCompiler/DM/Expressions/Constant.cs @@ -364,24 +364,32 @@ public Resource(Location location, string filePath) : base(location) { // Treat backslashes as forward slashes on Linux filePath = filePath.Replace('\\', '/'); - string? finalFilePath = null; - - var outputDir = System.IO.Path.GetDirectoryName(DMCompiler.Settings.Files[0]) ?? "/"; + var outputDir = System.IO.Path.GetDirectoryName(DMCompiler.Settings.Files?[0]) ?? "/"; if (string.IsNullOrEmpty(outputDir)) outputDir = "./"; + string? finalFilePath = null; + var fileName = System.IO.Path.GetFileName(filePath); var fileDir = System.IO.Path.GetDirectoryName(filePath) ?? string.Empty; - var directory = FindDirectory(outputDir, fileDir); - if (directory != null) { - // Perform a case-insensitive search for the file - finalFilePath = FindFile(directory, fileName); + + // Search every defined FILE_DIR + foreach (string resourceDir in DMCompiler.ResourceDirectories) { + var directory = FindDirectory(resourceDir, fileDir); + + if (directory != null) { + // Perform a case-insensitive search for the file + finalFilePath = FindFile(directory, fileName); + + if (finalFilePath != null) + break; + } } - // Search relative to the source file if it wasn't in the project's directory + // Search relative to the source file if it wasn't in one of the FILE_DIRs if (finalFilePath == null) { var sourceDir = System.IO.Path.Combine(outputDir, System.IO.Path.GetDirectoryName(Location.SourceFile) ?? string.Empty); - directory = FindDirectory(sourceDir, fileDir); + var directory = FindDirectory(sourceDir, fileDir); if (directory != null) finalFilePath = FindFile(directory, fileName); diff --git a/DMCompiler/DMCompiler.cs b/DMCompiler/DMCompiler.cs index 69844b001e..8f7ba89f4d 100644 --- a/DMCompiler/DMCompiler.cs +++ b/DMCompiler/DMCompiler.cs @@ -20,11 +20,13 @@ namespace DMCompiler { //TODO: Make this not a static class public static class DMCompiler { - public static int ErrorCount = 0; - public static int WarningCount = 0; + public static int ErrorCount; + public static int WarningCount; public static DMCompilerSettings Settings; + public static IReadOnlyList ResourceDirectories => _resourceDirectories; - private static DMCompilerConfiguration Config; + private static readonly DMCompilerConfiguration Config = new(); + private static readonly List _resourceDirectories = new(); private static DateTime _compileStartTime; public static bool Compile(DMCompilerSettings settings) { @@ -32,7 +34,8 @@ public static bool Compile(DMCompilerSettings settings) { WarningCount = 0; Settings = settings; if (Settings.Files == null) return false; - Config = new(); + Config.Reset(); + _resourceDirectories.Clear(); //TODO: Only use InvariantCulture where necessary instead of it being the default CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; @@ -50,25 +53,18 @@ public static bool Compile(DMCompilerSettings settings) { DMPreprocessor preprocessor = Preprocess(settings.Files, settings.MacroDefines); bool successfulCompile = preprocessor is not null && Compile(preprocessor); - if (successfulCompile) - { + if (successfulCompile) { //Output file is the first file with the extension changed to .json string outputFile = Path.ChangeExtension(settings.Files[0], "json"); List maps = ConvertMaps(preprocessor.IncludedMaps); - if (ErrorCount > 0) - { + if (ErrorCount > 0) { successfulCompile = false; - } - else - { + } else { var output = SaveJson(maps, preprocessor.IncludedInterface, outputFile); - if (ErrorCount > 0) - { + if (ErrorCount > 0) { successfulCompile = false; - } - else - { + } else { Console.WriteLine($"Compilation succeeded with {WarningCount} warnings"); Console.WriteLine(output); } @@ -80,13 +76,19 @@ public static bool Compile(DMCompilerSettings settings) { } TimeSpan duration = DateTime.Now - _compileStartTime; - Console.WriteLine($"Total time: {duration.ToString(@"mm\:ss")}"); + Console.WriteLine($"Total time: {duration:mm\\:ss}"); return successfulCompile; } - private static DMPreprocessor? Preprocess(List files, Dictionary macroDefines) { - DMPreprocessor? build() { + public static void AddResourceDirectory(string dir) { + dir = dir.Replace('\\', Path.DirectorySeparatorChar); + + _resourceDirectories.Add(dir); + } + + private static DMPreprocessor? Preprocess(List files, Dictionary? macroDefines) { + DMPreprocessor? Build() { DMPreprocessor preproc = new DMPreprocessor(true); if (macroDefines != null) { foreach (var (key, value) in macroDefines) { @@ -130,7 +132,7 @@ public static bool Compile(DMCompilerSettings settings) { if (Settings.DumpPreprocessor) { //Preprocessing is done twice because the output is used up when dumping it StringBuilder result = new(); - foreach (Token t in build()) { + foreach (Token t in Build()) { result.Append(t.Text); } @@ -140,7 +142,8 @@ public static bool Compile(DMCompilerSettings settings) { File.WriteAllText(outputPath, result.ToString()); Console.WriteLine($"Preprocessor output dumped to {outputPath}"); } - return build(); + + return Build(); } private static bool Compile(IEnumerable preprocessedTokens) { @@ -154,11 +157,6 @@ private static bool Compile(IEnumerable preprocessedTokens) { Emit(warning); } - if (astFile is null) { - VerbosePrint("Parsing failed, exiting compilation"); - return false; - } - DMASTSimplifier astSimplifier = new DMASTSimplifier(); VerbosePrint("Constant folding"); astSimplifier.SimplifyAST(astFile); @@ -190,7 +188,7 @@ public static void Emit(CompilerEmission emission) { /// Emits the given warning, according to its ErrorLevel as set in our config. /// True if the warning was an error, false if not. public static bool Emit(WarningCode code, Location loc, string message) { - ErrorLevel level = Config.errorConfig[code]; + ErrorLevel level = Config.ErrorConfig[code]; Emit(new CompilerEmission(level, code, loc, message)); return level == ErrorLevel.Error; } @@ -328,7 +326,7 @@ private static string SaveJson(List maps, string interfaceFile, st public static void DefineFatalErrors() { foreach (WarningCode code in Enum.GetValues()) { if((int)code < 1_000) { - Config.errorConfig[code] = ErrorLevel.Error; + Config.ErrorConfig[code] = ErrorLevel.Error; } } } @@ -338,32 +336,32 @@ public static void DefineFatalErrors() { /// public static void CheckAllPragmasWereSet() { foreach(WarningCode code in Enum.GetValues()) { - if (!Config.errorConfig.ContainsKey(code)) { + if (!Config.ErrorConfig.ContainsKey(code)) { ForcedWarning($"Warning #{(int)code:d4} '{code.ToString()}' was never declared as error, warning, notice, or disabled."); - Config.errorConfig.Add(code, ErrorLevel.Disabled); + Config.ErrorConfig.Add(code, ErrorLevel.Disabled); } } } public static void SetPragma(WarningCode code, ErrorLevel level) { - Config.errorConfig[code] = level; + Config.ErrorConfig[code] = level; } public static ErrorLevel CodeToLevel(WarningCode code) { - bool didFind = Config.errorConfig.TryGetValue(code, out var ret); + bool didFind = Config.ErrorConfig.TryGetValue(code, out var ret); DebugTools.Assert(didFind); return ret; } } public struct DMCompilerSettings { - public List Files = null; + public List? Files = null; public bool SuppressUnimplementedWarnings = false; public bool NoticesEnabled = false; public bool DumpPreprocessor = false; public bool NoStandard = false; public bool Verbose = false; - public Dictionary MacroDefines = null; + public Dictionary? MacroDefines = null; /// A user-provided pragma config file, if one was provided. public string? PragmaFileOverride = null; @@ -375,10 +373,11 @@ public DMCompilerSettings() { } } - class DMCompilerConfiguration { - public Dictionary errorConfig; - public DMCompilerConfiguration() { - errorConfig = new(Enum.GetValues().Length); + internal class DMCompilerConfiguration { + public readonly Dictionary ErrorConfig = new(Enum.GetValues().Length); + + public void Reset() { + ErrorConfig.Clear(); } } } From 4017375f65d3a32cc8ba7e8b0629968845315d5e Mon Sep 17 00:00:00 2001 From: pali <6pali6@gmail.com> Date: Wed, 4 Oct 2023 04:38:19 +0200 Subject: [PATCH 09/31] Const eval for some math procs (#1202) * add some math procs to IsValidRighthandSide * const eval test * abs test in const eval * yeet stuff from IsValidRighthandSide * const eval for math procs * formatting * stray using * github, tell me what the tantest error is pls * try ~= for comparison despite my doubts * use approximate equality because platform differences * remove sin etc. native proc implementations * document double precision usage * another place where we remove these from * fix merge * fixes * Apply suggestions from code review Co-authored-by: wixoa --------- Co-authored-by: amylizzle Co-authored-by: wixoa --- .../DMProject/Tests/Expression/const_eval.dm | 56 ++++ DMCompiler/Bytecode/DreamProcOpcode.cs | 13 +- DMCompiler/Compiler/DM/DMAST.cs | 155 ++++++++++- DMCompiler/Compiler/DM/DMParser.cs | 49 ++++ DMCompiler/DM/DMProc.cs | 46 ++++ DMCompiler/DM/Expressions/Builtins.cs | 250 ++++++++++++++++++ DMCompiler/DM/Expressions/Constant.cs | 108 ++++++++ DMCompiler/DM/Visitors/DMVisitorExpression.cs | 55 ++++ DMCompiler/DMStandard/_Standard.dm | 9 - OpenDreamRuntime/Procs/DMOpcodeHandlers.cs | 80 ++++++ OpenDreamRuntime/Procs/DMProc.cs | 11 + .../Procs/Native/DreamProcNative.cs | 9 - .../Procs/Native/DreamProcNativeRoot.cs | 102 ------- 13 files changed, 821 insertions(+), 122 deletions(-) create mode 100644 Content.Tests/DMProject/Tests/Expression/const_eval.dm diff --git a/Content.Tests/DMProject/Tests/Expression/const_eval.dm b/Content.Tests/DMProject/Tests/Expression/const_eval.dm new file mode 100644 index 0000000000..abe38dbb81 --- /dev/null +++ b/Content.Tests/DMProject/Tests/Expression/const_eval.dm @@ -0,0 +1,56 @@ +/datum + var/composite_expr = 4 * sin(3 + cos(2)) + var/sintest = sin(45) + var/costest = cos(123) + var/tantest = tan(123) + var/sqrttest = sqrt(123) + var/arcsintest = arcsin(sin(45)) + var/arccostest = arccos(cos(123)) + var/arctantest = arctan(tan(69)) + var/log_test = log(10) + var/log10_test = log(10, 100) + var/arctan2_test = arctan(1, 3) + var/abs_test = abs(-213) + +#define EPSILON 4e-6 +#define APX_EQUAL(a, b) ASSERT(abs(a - b) < EPSILON) + +/proc/RunTest() + var/break_const_eval = null + var/datum/d = new /datum + + APX_EQUAL(initial(d.composite_expr), 4 * sin(3 + cos(break_const_eval || 2))) + APX_EQUAL(d.composite_expr, 4 * sin(3 + cos(break_const_eval || 2))) + + APX_EQUAL(d.sintest, sin(break_const_eval || 45)) + APX_EQUAL(d.sintest, 0.707106769084930419921875) + + APX_EQUAL(d.costest, cos(break_const_eval || 123)) + APX_EQUAL(d.costest, -0.544639050960540771484375) + + APX_EQUAL(d.tantest, tan(break_const_eval || 123)) + APX_EQUAL(d.tantest, -1.539865016937255859375) + + APX_EQUAL(d.sqrttest, sqrt(break_const_eval || 123)) + APX_EQUAL(d.sqrttest, 11.0905361175537109375) + + APX_EQUAL(d.arcsintest, arcsin(sin(break_const_eval || 45))) + APX_EQUAL(d.arcsintest, 45) + + APX_EQUAL(d.arccostest, arccos(cos(break_const_eval || 123))) + APX_EQUAL(d.arccostest, 123) + + APX_EQUAL(d.arctantest, arctan(tan(break_const_eval || 69))) + APX_EQUAL(d.arctantest, 69) + + APX_EQUAL(d.log_test, log(break_const_eval || 10)) + APX_EQUAL(d.log_test, 2.302585124969482421875) + + APX_EQUAL(d.log10_test, log(break_const_eval || 10, 100)) + APX_EQUAL(d.log10_test, 2) + + APX_EQUAL(d.arctan2_test, arctan(break_const_eval || 1, 3)) + APX_EQUAL(d.arctan2_test, 71.5650482177734375) + + APX_EQUAL(d.abs_test, abs(break_const_eval || -213)) + \ No newline at end of file diff --git a/DMCompiler/Bytecode/DreamProcOpcode.cs b/DMCompiler/Bytecode/DreamProcOpcode.cs index 57df1fb0e0..53ec087bba 100644 --- a/DMCompiler/Bytecode/DreamProcOpcode.cs +++ b/DMCompiler/Bytecode/DreamProcOpcode.cs @@ -127,7 +127,18 @@ public enum DreamProcOpcode : byte { GetStep = 0x75, Length = 0x76, GetDir = 0x77, - DebuggerBreakpoint = 0x78 + DebuggerBreakpoint = 0x78, + Sin = 0x79, + Cos = 0x7A, + Tan = 0x7B, + Arcsin = 0x7C, + Arccos = 0x7D, + Arctan = 0x7E, + Arctan2 = 0x7F, + Sqrt = 0x80, + Log = 0x81, + LogE = 0x82, + Abs = 0x83, } /// diff --git a/DMCompiler/Compiler/DM/DMAST.cs b/DMCompiler/Compiler/DM/DMAST.cs index 5ad40281e0..b60eb9a1be 100644 --- a/DMCompiler/Compiler/DM/DMAST.cs +++ b/DMCompiler/Compiler/DM/DMAST.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; using System.Linq; using OpenDreamShared.Compiler; using OpenDreamShared.Dream; @@ -262,7 +263,36 @@ public void VisitGradient(DMASTGradient gradient) { public void VisitPick(DMASTPick pick) { throw new NotImplementedException(); } - + public void VisitSin(DMASTSin sin) { + throw new NotImplementedException(); + } + public void VisitCos(DMASTCos cos) { + throw new NotImplementedException(); + } + public void VisitTan(DMASTTan tan) { + throw new NotImplementedException(); + } + public void VisitArcsin(DMASTArcsin asin) { + throw new NotImplementedException(); + } + public void VisitArccos(DMASTArccos acos) { + throw new NotImplementedException(); + } + public void VisitArctan(DMASTArctan atan) { + throw new NotImplementedException(); + } + public void VisitArctan2(DMASTArctan2 atan) { + throw new NotImplementedException(); + } + public void VisitSqrt(DMASTSqrt sqrt) { + throw new NotImplementedException(); + } + public void VisitLog(DMASTLog log) { + throw new NotImplementedException(); + } + public void VisitAbs(DMASTAbs abs) { + throw new NotImplementedException(); + } public void VisitCall(DMASTCall call) { throw new NotImplementedException(); } @@ -1554,6 +1584,129 @@ public override void Visit(DMASTVisitor visitor) { } } + public class DMASTSin : DMASTExpression { + public DMASTExpression Expression; + + public DMASTSin(Location location, DMASTExpression expression) : base(location) { + Expression = expression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitSin(this); + } + } + + public class DMASTCos : DMASTExpression { + public DMASTExpression Expression; + + public DMASTCos(Location location, DMASTExpression expression) : base(location) { + Expression = expression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitCos(this); + } + } + + public class DMASTTan : DMASTExpression { + public DMASTExpression Expression; + + public DMASTTan(Location location, DMASTExpression expression) : base(location) { + Expression = expression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitTan(this); + } + } + + public class DMASTArcsin : DMASTExpression { + public DMASTExpression Expression; + + public DMASTArcsin(Location location, DMASTExpression expression) : base(location) { + Expression = expression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitArcsin(this); + } + } + + public class DMASTArccos : DMASTExpression { + public DMASTExpression Expression; + + public DMASTArccos(Location location, DMASTExpression expression) : base(location) { + Expression = expression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitArccos(this); + } + } + + public class DMASTArctan : DMASTExpression { + public DMASTExpression Expression; + + public DMASTArctan(Location location, DMASTExpression expression) : base(location) { + Expression = expression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitArctan(this); + } + } + + public class DMASTArctan2 : DMASTExpression { + public DMASTExpression XExpression; + public DMASTExpression YExpression; + + public DMASTArctan2(Location location, DMASTExpression xExpression, DMASTExpression yExpression) : base(location) { + XExpression = xExpression; + YExpression = yExpression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitArctan2(this); + } + } + + public class DMASTSqrt : DMASTExpression { + public DMASTExpression Expression; + + public DMASTSqrt(Location location, DMASTExpression expression) : base(location) { + Expression = expression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitSqrt(this); + } + } + + public class DMASTLog : DMASTExpression { + public DMASTExpression Expression; + public DMASTExpression? BaseExpression; + + public DMASTLog(Location location, DMASTExpression expression, DMASTExpression? baseExpression) : base(location) { + Expression = expression; + BaseExpression = baseExpression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitLog(this); + } + } + + public class DMASTAbs : DMASTExpression { + public DMASTExpression Expression; + + public DMASTAbs(Location location, DMASTExpression expression) : base(location) { + Expression = expression; + } + + public override void Visit(DMASTVisitor visitor) { + visitor.VisitAbs(this); + } + } public sealed class DMASTCall : DMASTExpression { public readonly DMASTCallParameter[] CallParameters, ProcParameters; diff --git a/DMCompiler/Compiler/DM/DMParser.cs b/DMCompiler/Compiler/DM/DMParser.cs index e1b9d8d8de..66990b5a7e 100644 --- a/DMCompiler/Compiler/DM/DMParser.cs +++ b/DMCompiler/Compiler/DM/DMParser.cs @@ -2412,6 +2412,55 @@ private DMASTExpression ParseProcCall(DMASTExpression expression) { return new DMASTIsSaved(identifier.Location, callParameters[0].Value); } + case "sin": { + if (callParameters.Length != 1) Error("sin() requires 1 argument"); + + return new DMASTSin(identifier.Location, callParameters[0].Value); + } + case "cos": { + if (callParameters.Length != 1) Error("cos() requires 1 argument"); + + return new DMASTCos(identifier.Location, callParameters[0].Value); + } + case "tan": { + if (callParameters.Length != 1) Error("tan() requires 1 argument"); + + return new DMASTTan(identifier.Location, callParameters[0].Value); + } + case "arcsin": { + if (callParameters.Length != 1) Error("arcsin() requires 1 argument"); + + return new DMASTArcsin(identifier.Location, callParameters[0].Value); + } + case "arccos": { + if (callParameters.Length != 1) Error("arccos() requires 1 argument"); + + return new DMASTArccos(identifier.Location, callParameters[0].Value); + } + case "arctan": { + if (callParameters.Length != 1 && callParameters.Length != 2) + Error("arctan() requires 1 or 2 arguments"); + if (callParameters.Length == 1) + return new DMASTArctan(identifier.Location, callParameters[0].Value); + return new DMASTArctan2(identifier.Location, callParameters[0].Value, callParameters[1].Value); + } + case "sqrt": { + if (callParameters.Length != 1) Error("sqrt() requires 1 argument"); + + return new DMASTSqrt(identifier.Location, callParameters[0].Value); + } + case "log": { + if (callParameters.Length != 1 && callParameters.Length != 2) + Error("log() requires 1 or 2 arguments"); + if (callParameters.Length == 1) + return new DMASTLog(identifier.Location, callParameters[0].Value, null); + return new DMASTLog(identifier.Location, callParameters[1].Value, callParameters[0].Value); + } + case "abs": { + if (callParameters.Length != 1) Error("abs() requires 1 argument"); + + return new DMASTAbs(identifier.Location, callParameters[0].Value); + } case "istype": { if (callParameters.Length == 1) { return new DMASTImplicitIsType(identifier.Location, callParameters[0].Value); diff --git a/DMCompiler/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs index 06b6f9e263..5c4a58f996 100644 --- a/DMCompiler/DM/DMProc.cs +++ b/DMCompiler/DM/DMProc.cs @@ -945,6 +945,52 @@ public void LessThanOrEqual() { WriteOpcode(DreamProcOpcode.CompareLessThanOrEqual); } + public void Sin() { + WriteOpcode(DreamProcOpcode.Sin); + } + + public void Cos() { + WriteOpcode(DreamProcOpcode.Cos); + } + + public void Tan() { + WriteOpcode(DreamProcOpcode.Tan); + } + + public void Arcsin() { + WriteOpcode(DreamProcOpcode.Arcsin); + } + + public void Arccos() { + WriteOpcode(DreamProcOpcode.Arccos); + } + + public void Arctan() { + WriteOpcode(DreamProcOpcode.Arctan); + } + + public void Arctan2() { + ShrinkStack(1); + WriteOpcode(DreamProcOpcode.Arctan2); + } + + public void Sqrt() { + WriteOpcode(DreamProcOpcode.Sqrt); + } + + public void Log() { + ShrinkStack(1); + WriteOpcode(DreamProcOpcode.Log); + } + + public void LogE() { + WriteOpcode(DreamProcOpcode.LogE); + } + + public void Abs() { + WriteOpcode(DreamProcOpcode.Abs); + } + public void PushFloat(float value) { GrowStack(1); WriteOpcode(DreamProcOpcode.PushFloat); diff --git a/DMCompiler/DM/Expressions/Builtins.cs b/DMCompiler/DM/Expressions/Builtins.cs index ece83bc408..280dd67e09 100644 --- a/DMCompiler/DM/Expressions/Builtins.cs +++ b/DMCompiler/DM/Expressions/Builtins.cs @@ -643,4 +643,254 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { proc.PushProc(proc.Id); } } + + class Sin : DMExpression { + DMExpression _expr; + + public Sin(Location location, DMExpression expr) : base(location) { + _expr = expr; + } + + public override bool TryAsConstant(out Constant constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + constant = constant.Sin(); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + proc.Sin(); + } + } + + class Cos : DMExpression { + DMExpression _expr; + + public Cos(Location location, DMExpression expr) : base(location) { + _expr = expr; + } + + public override bool TryAsConstant(out Constant constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + constant = constant.Cos(); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + proc.Cos(); + } + } + + class Tan : DMExpression { + DMExpression _expr; + + public Tan(Location location, DMExpression expr) : base(location) { + _expr = expr; + } + + public override bool TryAsConstant(out Constant constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + constant = constant.Tan(); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + proc.Tan(); + } + } + + class Arcsin : DMExpression { + DMExpression _expr; + + public Arcsin(Location location, DMExpression expr) : base(location) { + _expr = expr; + } + + public override bool TryAsConstant(out Constant constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + constant = constant.Arcsin(); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + proc.Arcsin(); + } + } + + class Arccos : DMExpression { + DMExpression _expr; + + public Arccos(Location location, DMExpression expr) : base(location) { + _expr = expr; + } + + public override bool TryAsConstant(out Constant constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + constant = constant.Arccos(); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + proc.Arccos(); + } + } + + class Arctan : DMExpression { + DMExpression _expr; + + public Arctan(Location location, DMExpression expr) : base(location) { + _expr = expr; + } + + public override bool TryAsConstant(out Constant constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + constant = constant.Arctan(); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + proc.Arctan(); + } + } + + class Arctan2 : DMExpression { + DMExpression _xexpr; + DMExpression _yexpr; + + public Arctan2(Location location, DMExpression xexpr, DMExpression yexpr) : base(location) { + _xexpr = xexpr; + _yexpr = yexpr; + } + + public override bool TryAsConstant(out Constant constant) { + if (!_xexpr.TryAsConstant(out Constant xconst) || !_yexpr.TryAsConstant(out Constant yconst)) { + constant = null; + return false; + } + + constant = xconst.Arctan2(yconst); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _xexpr.EmitPushValue(dmObject, proc); + _yexpr.EmitPushValue(dmObject, proc); + proc.Arctan2(); + } + } + + class Sqrt : DMExpression { + DMExpression _expr; + + public Sqrt(Location location, DMExpression expr) : base(location) { + _expr = expr; + } + + public override bool TryAsConstant(out Constant constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + constant = constant.Sqrt(); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + proc.Sqrt(); + } + } + + class Log : DMExpression { + DMExpression _expr; + DMExpression? _baseExpr; + + public Log(Location location, DMExpression expr, DMExpression? baseExpr) : base(location) { + _expr = expr; + _baseExpr = baseExpr; + } + + public override bool TryAsConstant(out Constant constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + if (_baseExpr == null) { + constant = constant.Log(null); + return true; + } + + if (!_baseExpr.TryAsConstant(out Constant baseConstant)) { + constant = null; + return false; + } + + constant = constant.Log(baseConstant); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + if (_baseExpr == null) { + proc.LogE(); + } else { + _baseExpr.EmitPushValue(dmObject, proc); + proc.Log(); + } + } + } + + class Abs : DMExpression { + DMExpression _expr; + + public Abs(Location location, DMExpression expr) : base(location) { + _expr = expr; + } + + public override bool TryAsConstant(out Constant constant) { + if (!_expr.TryAsConstant(out constant)) { + constant = null; + return false; + } + + constant = constant.Abs(); + return true; + } + + public override void EmitPushValue(DMObject dmObject, DMProc proc) { + _expr.EmitPushValue(dmObject, proc); + proc.Abs(); + } + } } diff --git a/DMCompiler/DM/Expressions/Constant.cs b/DMCompiler/DM/Expressions/Constant.cs index 1894e2145f..f7d00a87ae 100644 --- a/DMCompiler/DM/Expressions/Constant.cs +++ b/DMCompiler/DM/Expressions/Constant.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; using System.IO; namespace DMCompiler.DM.Expressions { @@ -96,6 +97,49 @@ public virtual Constant LessThan(Constant rhs) { public virtual Constant LessThanOrEqual(Constant rhs) { throw new CompileErrorException(Location, $"const operation \"{this} <= {rhs}\" is invalid"); } + + public virtual Constant Sin() { + throw new CompileErrorException(Location, $"const operation \"sin({this})\" is invalid"); + } + + public virtual Constant Cos() { + throw new CompileErrorException(Location, $"const operation \"cos({this})\" is invalid"); + } + + public virtual Constant Tan() { + throw new CompileErrorException(Location, $"const operation \"tan({this})\" is invalid"); + } + + public virtual Constant Arcsin() { + throw new CompileErrorException(Location, $"const operation \"arcsin({this})\" is invalid"); + } + + public virtual Constant Arccos() { + throw new CompileErrorException(Location, $"const operation \"arccos({this})\" is invalid"); + } + + public virtual Constant Arctan() { + throw new CompileErrorException(Location, $"const operation \"arctan({this})\" is invalid"); + } + + public virtual Constant Arctan2(Constant yConst) { + throw new CompileErrorException(Location, $"const operation \"arctan({this}, {yConst})\" is invalid"); + } + + public virtual Constant Sqrt() { + throw new CompileErrorException(Location, $"const operation \"sqrt({this})\" is invalid"); + } + + public virtual Constant Log(Constant? baseVal) { + if (baseVal == null) { + throw new CompileErrorException(Location, $"const operation \"log({this})\" is invalid"); + } + throw new CompileErrorException(Location, $"const operation \"log({baseVal}, {this})\" is invalid"); + } + + public virtual Constant Abs() { + throw new CompileErrorException(Location, $"const operation \"abs({this})\" is invalid"); + } #endregion } @@ -315,6 +359,70 @@ public override Constant LessThanOrEqual(Constant rhs) { } return new Number(Location, (Value <= rhsNum.Value) ? 1 : 0); } + + public override Constant Sin() { + return new Number(Location, MathF.Sin(Value / 180 * MathF.PI)); + } + + public override Constant Cos() { + return new Number(Location, MathF.Cos(Value / 180 * MathF.PI)); + } + + public override Constant Tan() { + return new Number(Location, MathF.Tan(Value / 180 * MathF.PI)); + } + + public override Constant Arcsin() { + if (Value < -1 || Value > 1) { + throw new CompileErrorException(Location, $"const operation \"arcsin({this})\" is invalid (out of range)"); + } + return new Number(Location, MathF.Asin(Value) / MathF.PI * 180); + } + + public override Constant Arccos() { + if (Value < -1 || Value > 1) { + throw new CompileErrorException(Location, $"const operation \"arccos({this})\" is invalid (out of range)"); + } + return new Number(Location, MathF.Acos(Value) / MathF.PI * 180); + } + + public override Constant Arctan() { + return new Number(Location, MathF.Atan(Value) / MathF.PI * 180); + } + + public override Constant Arctan2(Constant yConst) { + if (yConst is not Number yNum) { + throw new CompileErrorException(Location, $"const operation \"arctan2({this}, {yConst})\" is invalid"); + } + return new Number(Location, MathF.Atan2(yNum.Value, Value) / MathF.PI * 180); + } + + public override Constant Sqrt() { + if (Value < 0) { + throw new CompileErrorException(Location, $"const operation \"sqrt({this})\" is invalid (negative)"); + } + return new Number(Location, MathF.Sqrt(Value)); + } + + public override Constant Log(Constant? baseVal) { + if (Value <= 0) { + throw new CompileErrorException(Location, $"const operation \"log({this})\" is invalid (non-positive)"); + } + if (baseVal == null) { + return new Number(Location, MathF.Log(Value)); + } + if (baseVal is not Number baseNum) { + throw new CompileErrorException(Location, $"const operation \"log({this}, {baseVal})\" is invalid"); + } + if (baseNum.Value <= 0) { + throw new CompileErrorException(Location, $"const operation \"log({this}, {baseVal})\" is invalid (non-positive base)"); + } + return new Number(Location, MathF.Log(Value, baseNum.Value)); + } + + public override Constant Abs() { + return new Number(Location, MathF.Abs(Value)); + } } // "abc" diff --git a/DMCompiler/DM/Visitors/DMVisitorExpression.cs b/DMCompiler/DM/Visitors/DMVisitorExpression.cs index fcd6dc9514..9d711dc4c3 100644 --- a/DMCompiler/DM/Visitors/DMVisitorExpression.cs +++ b/DMCompiler/DM/Visitors/DMVisitorExpression.cs @@ -978,6 +978,61 @@ public void VisitPick(DMASTPick pick) { Result = new Expressions.Pick(pick.Location, pickValues); } + + public void VisitSin(DMASTSin sin) { + var expr = DMExpression.Create(_dmObject, _proc, sin.Expression, _inferredPath); + Result = new Expressions.Sin(sin.Location, expr); + } + + public void VisitCos(DMASTCos cos) { + var expr = DMExpression.Create(_dmObject, _proc, cos.Expression, _inferredPath); + Result = new Expressions.Cos(cos.Location, expr); + } + + public void VisitTan(DMASTTan tan) { + var expr = DMExpression.Create(_dmObject, _proc, tan.Expression, _inferredPath); + Result = new Expressions.Tan(tan.Location, expr); + } + + public void VisitArcsin(DMASTArcsin arcsin) { + var expr = DMExpression.Create(_dmObject, _proc, arcsin.Expression, _inferredPath); + Result = new Expressions.Arcsin(arcsin.Location, expr); + } + + public void VisitArccos(DMASTArccos arccos) { + var expr = DMExpression.Create(_dmObject, _proc, arccos.Expression, _inferredPath); + Result = new Expressions.Arccos(arccos.Location, expr); + } + + public void VisitArctan(DMASTArctan arctan) { + var expr = DMExpression.Create(_dmObject, _proc, arctan.Expression, _inferredPath); + Result = new Expressions.Arctan(arctan.Location, expr); + } + + public void VisitArctan2(DMASTArctan2 arctan2) { + var xexpr = DMExpression.Create(_dmObject, _proc, arctan2.XExpression, _inferredPath); + var yexpr = DMExpression.Create(_dmObject, _proc, arctan2.YExpression, _inferredPath); + Result = new Expressions.Arctan2(arctan2.Location, xexpr, yexpr); + } + + public void VisitSqrt(DMASTSqrt sqrt) { + var expr = DMExpression.Create(_dmObject, _proc, sqrt.Expression, _inferredPath); + Result = new Expressions.Sqrt(sqrt.Location, expr); + } + + public void VisitLog(DMASTLog log) { + var expr = DMExpression.Create(_dmObject, _proc, log.Expression, _inferredPath); + DMExpression? baseExpr = null; + if (log.BaseExpression != null) { + baseExpr = DMExpression.Create(_dmObject, _proc, log.BaseExpression, _inferredPath); + } + Result = new Expressions.Log(log.Location, expr, baseExpr); + } + + public void VisitAbs(DMASTAbs abs) { + var expr = DMExpression.Create(_dmObject, _proc, abs.Expression, _inferredPath); + Result = new Expressions.Abs(abs.Location, expr); + } public void VisitCall(DMASTCall call) { var procArgs = new ArgumentList(call.Location, _dmObject, _proc, call.ProcParameters, _inferredPath); diff --git a/DMCompiler/DMStandard/_Standard.dm b/DMCompiler/DMStandard/_Standard.dm index 39c834b989..5295ea0176 100644 --- a/DMCompiler/DMStandard/_Standard.dm +++ b/DMCompiler/DMStandard/_Standard.dm @@ -1,13 +1,9 @@ /var/world/world = null //These procs should be in alphabetical order, as in DreamProcNativeRoot.cs -proc/abs(A) proc/addtext(...) proc/alert(Usr = usr, Message, Title, Button1 = "Ok", Button2, Button3) proc/animate(Object, time, loop, easing, flags) -proc/arccos(X) -proc/arcsin(X) -proc/arctan(A) proc/ascii2text(N) proc/block(atom/Start, atom/End, StartZ, EndX=Start, EndY=End, EndZ=StartZ) proc/ceil(A) @@ -17,7 +13,6 @@ proc/clamp(Value, Low, High) proc/cmptext(T1) proc/copytext(T, Start = 1, End = 0) proc/copytext_char(T,Start=1,End=0) -proc/cos(X) proc/CRASH(msg) proc/fcopy(Src, Dst) proc/fcopy_rsc(File) @@ -63,7 +58,6 @@ proc/json_encode(Value) proc/length(E) proc/length_char(E) proc/list2params(List) -proc/log(X, Y) proc/lowertext(T) proc/max(A) proc/md5(T) @@ -86,7 +80,6 @@ proc/roll(ndice = 1, sides) proc/round(A, B) proc/sha1(input) proc/shutdown(Addr,Natural = 0) -proc/sin(X) proc/sleep(Delay) proc/sorttext(T1, T2) proc/sorttextEx(T1, T2) @@ -96,10 +89,8 @@ proc/spantext_char(Haystack,Needles,Start=1) proc/splicetext(Text, Start = 1, End = 0, Insert = "") proc/splicetext_char(Text, Start = 1, End = 0, Insert = "") proc/splittext(Text, Delimiter) -proc/sqrt(A) proc/stat(Name, Value) proc/statpanel(Panel, Name, Value) -proc/tan(X) proc/text2ascii(T, pos = 1) proc/text2ascii_char(T, pos = 1) proc/text2file(Text, File) diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index e35bba408e..4bd131d69a 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -1618,6 +1618,86 @@ public static ProcStatus EndTry(DMProcState state) { return ProcStatus.Continue; } + public static ProcStatus Sin(DMProcState state) { + DreamValue value = state.Pop(); + + state.Push(new DreamValue(MathF.Sin(value.UnsafeGetValueAsFloat() / 180 * MathF.PI))); + return ProcStatus.Continue; + } + + public static ProcStatus Cos(DMProcState state) { + DreamValue value = state.Pop(); + + state.Push(new DreamValue(MathF.Cos(value.UnsafeGetValueAsFloat() / 180 * MathF.PI))); + return ProcStatus.Continue; + } + + public static ProcStatus Tan(DMProcState state) { + DreamValue value = state.Pop(); + + state.Push(new DreamValue(MathF.Tan(value.UnsafeGetValueAsFloat() / 180 * MathF.PI))); + return ProcStatus.Continue; + } + + public static ProcStatus Arcsin(DMProcState state) { + DreamValue value = state.Pop(); + + state.Push(new DreamValue(MathF.Asin(value.UnsafeGetValueAsFloat()) / MathF.PI * 180)); + return ProcStatus.Continue; + } + + public static ProcStatus Arccos(DMProcState state) { + DreamValue value = state.Pop(); + + state.Push(new DreamValue(MathF.Acos(value.UnsafeGetValueAsFloat()) / MathF.PI * 180)); + return ProcStatus.Continue; + } + + public static ProcStatus Arctan(DMProcState state) { + DreamValue value = state.Pop(); + + state.Push(new DreamValue(MathF.Atan(value.UnsafeGetValueAsFloat()) / MathF.PI * 180)); + return ProcStatus.Continue; + } + + public static ProcStatus Arctan2(DMProcState state) { + DreamValue y = state.Pop(); + DreamValue x = state.Pop(); + var yValue = y.UnsafeGetValueAsFloat(); + + state.Push(new DreamValue(MathF.Atan2(yValue, x.UnsafeGetValueAsFloat()) / MathF.PI * 180)); + return ProcStatus.Continue; + } + + public static ProcStatus Sqrt(DMProcState state) { + DreamValue value = state.Pop(); + + state.Push(new DreamValue(MathF.Sqrt(value.UnsafeGetValueAsFloat()))); + return ProcStatus.Continue; + } + + public static ProcStatus Log(DMProcState state) { + DreamValue baseValue = state.Pop(); + DreamValue value = state.Pop(); + + state.Push(new DreamValue(MathF.Log(value.UnsafeGetValueAsFloat(), baseValue.UnsafeGetValueAsFloat()))); + return ProcStatus.Continue; + } + + public static ProcStatus LogE(DMProcState state) { + DreamValue value = state.Pop(); + + state.Push(new DreamValue(MathF.Log(value.UnsafeGetValueAsFloat()))); + return ProcStatus.Continue; + } + + public static ProcStatus Abs(DMProcState state) { + DreamValue value = state.Pop(); + + state.Push(new DreamValue(MathF.Abs(value.UnsafeGetValueAsFloat()))); + return ProcStatus.Continue; + } + public static ProcStatus SwitchCase(DMProcState state) { int casePosition = state.ReadInt(); DreamValue testValue = state.Pop(); diff --git a/OpenDreamRuntime/Procs/DMProc.cs b/OpenDreamRuntime/Procs/DMProc.cs index 086caa5ac6..37be0c73c8 100644 --- a/OpenDreamRuntime/Procs/DMProc.cs +++ b/OpenDreamRuntime/Procs/DMProc.cs @@ -285,6 +285,17 @@ public sealed class DMProcState : ProcState { {DreamProcOpcode.TryNoValue, DMOpcodeHandlers.TryNoValue}, {DreamProcOpcode.EndTry, DMOpcodeHandlers.EndTry}, {DreamProcOpcode.Gradient, DMOpcodeHandlers.Gradient}, + {DreamProcOpcode.Sin, DMOpcodeHandlers.Sin}, + {DreamProcOpcode.Cos, DMOpcodeHandlers.Cos}, + {DreamProcOpcode.Tan, DMOpcodeHandlers.Tan}, + {DreamProcOpcode.Arcsin, DMOpcodeHandlers.Arcsin}, + {DreamProcOpcode.Arccos, DMOpcodeHandlers.Arccos}, + {DreamProcOpcode.Arctan, DMOpcodeHandlers.Arctan}, + {DreamProcOpcode.Arctan2, DMOpcodeHandlers.Arctan2}, + {DreamProcOpcode.Sqrt, DMOpcodeHandlers.Sqrt}, + {DreamProcOpcode.Log, DMOpcodeHandlers.Log}, + {DreamProcOpcode.LogE, DMOpcodeHandlers.LogE}, + {DreamProcOpcode.Abs, DMOpcodeHandlers.Abs}, {DreamProcOpcode.EnumerateNoAssign, DMOpcodeHandlers.EnumerateNoAssign}, {DreamProcOpcode.GetStep, DMOpcodeHandlers.GetStep}, {DreamProcOpcode.Length, DMOpcodeHandlers.Length}, diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs index c065d55fbe..1b814316f7 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs @@ -3,12 +3,8 @@ namespace OpenDreamRuntime.Procs.Native { internal static class DreamProcNative { public static void SetupNativeProcs(DreamObjectTree objectTree) { - objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_abs); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_alert); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_animate); - objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_arccos); - objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_arcsin); - objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_arctan); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_ascii2text); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_block); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_ceil); @@ -18,7 +14,6 @@ public static void SetupNativeProcs(DreamObjectTree objectTree) { objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_cmptext); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_copytext); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_copytext_char); - objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_cos); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_CRASH); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_fcopy); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_fcopy_rsc); @@ -60,7 +55,6 @@ public static void SetupNativeProcs(DreamObjectTree objectTree) { objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_json_encode); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_length_char); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_list2params); - objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_log); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_lowertext); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_matrix); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_max); @@ -85,7 +79,6 @@ public static void SetupNativeProcs(DreamObjectTree objectTree) { objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_round); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_sha1); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_shutdown); - objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_sin); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_sleep); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_sorttext); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_sorttextEx); @@ -95,10 +88,8 @@ public static void SetupNativeProcs(DreamObjectTree objectTree) { objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_splicetext); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_splicetext_char); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_splittext); - objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_sqrt); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_stat); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_statpanel); - objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_tan); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_text2ascii); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_text2ascii_char); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_text2file); diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs index 22de0d1361..d3c7d7133a 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs @@ -30,14 +30,6 @@ namespace OpenDreamRuntime.Procs.Native { /// like filter(), matrix(), etc. /// internal static class DreamProcNativeRoot { - [DreamProc("abs")] - [DreamProcParameter("A", Type = DreamValueTypeFlag.Float)] - public static DreamValue NativeProc_abs(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - bundle.GetArgument(0, "A").TryGetValueAsFloat(out var number); - - return new DreamValue(Math.Abs(number)); - } - [DreamProc("alert")] [DreamProcParameter("Usr", Type = DreamValueTypeFlag.DreamObject)] [DreamProcParameter("Message", Type = DreamValueTypeFlag.String)] @@ -136,49 +128,6 @@ public static DreamValue NativeProc_animate(NativeProc.Bundle bundle, DreamObjec return DreamValue.Null; } - /* NOTE ABOUT THE TRIG FUNCTIONS: - * If you have a sharp eye, you may notice that our trigonometry functions make use of the *double*-precision versions of those functions, - * even though this is a single-precision language. - * - * DO NOT replace them with the single-precision ones in MathF!!! - * - * BYOND erroneously calls the double-precision versions in its code, in a way that does honestly affect behaviour in some circumstances. - * Replicating that REQUIRES us to do the same error! You will break a unit test or two if you try to change this. - */ - - [DreamProc("arccos")] - [DreamProcParameter("X", Type = DreamValueTypeFlag.Float)] - public static DreamValue NativeProc_arccos(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - bundle.GetArgument(0, "X").TryGetValueAsFloat(out float x); - double acos = Math.Acos(x); - - return new DreamValue((float)(acos * 180 / Math.PI)); - } - - [DreamProc("arcsin")] - [DreamProcParameter("X", Type = DreamValueTypeFlag.Float)] - public static DreamValue NativeProc_arcsin(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - bundle.GetArgument(0, "X").TryGetValueAsFloat(out float x); - double asin = Math.Asin(x); - - return new DreamValue((float)(asin * 180 / Math.PI)); - } - - [DreamProc("arctan")] - [DreamProcParameter("x", Type = DreamValueTypeFlag.Float)] - [DreamProcParameter("y", Type = DreamValueTypeFlag.Float)] - public static DreamValue NativeProc_arctan(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - bundle.GetArgument(0, "x").TryGetValueAsFloat(out float x); - double atan; - if (bundle.Arguments.Length == 1) { - atan = Math.Atan(x); - } else { - bundle.GetArgument(1, "y").TryGetValueAsFloat(out float y); - atan = Math.Atan2(y, x); - } - return new DreamValue((float)(atan * 180 / Math.PI)); - } - [DreamProc("ascii2text")] [DreamProcParameter("N", Type = DreamValueTypeFlag.Float)] public static DreamValue NativeProc_ascii2text(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { @@ -400,15 +349,6 @@ public static DreamValue NativeProc_copytext_char(NativeProc.Bundle bundle, Drea return new DreamValue(textElements.SubstringByTextElements(start - 1, end - start)); } - [DreamProc("cos")] - [DreamProcParameter("X", Type = DreamValueTypeFlag.Float)] - public static DreamValue NativeProc_cos(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - bundle.GetArgument(0, "X").TryGetValueAsFloat(out float x); - double rad = x * (Math.PI / 180); - - return new DreamValue((float)Math.Cos(rad)); - } - [DreamProc("CRASH")] [DreamProcParameter("msg", Type = DreamValueTypeFlag.String)] public static DreamValue NativeProc_CRASH(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { @@ -1273,22 +1213,6 @@ public static DreamValue NativeProc_list2params(NativeProc.Bundle bundle, DreamO return new DreamValue(list2params(list)); } - [DreamProc("log")] - [DreamProcParameter("X", Type = DreamValueTypeFlag.Float)] - [DreamProcParameter("Y")] - public static DreamValue NativeProc_log(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - bundle.GetArgument(0, "X").TryGetValueAsFloat(out float x); - DreamValue yValue = bundle.GetArgument(1, "Y"); - - if (!yValue.IsNull) { - yValue.TryGetValueAsFloat(out float y); - - return new DreamValue((float)Math.Log(y, x)); - } else { - return new DreamValue(Math.Log(x)); - } - } - [DreamProc("lowertext")] [DreamProcParameter("T", Type = DreamValueTypeFlag.String)] public static DreamValue NativeProc_lowertext(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { @@ -2218,15 +2142,6 @@ public static DreamValue NativeProc_shutdown(NativeProc.Bundle bundle, DreamObje return DreamValue.Null; } - [DreamProc("sin")] - [DreamProcParameter("X", Type = DreamValueTypeFlag.Float)] - public static DreamValue NativeProc_sin(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - bundle.GetArgument(0, "X").TryGetValueAsFloat(out var x); - double rad = x * (Math.PI / 180); - - return new DreamValue((float)Math.Sin(rad)); - } - [DreamProc("sleep")] [DreamProcParameter("Delay", Type = DreamValueTypeFlag.Float)] public static async Task NativeProc_sleep(AsyncNativeProc.State state) { @@ -2461,14 +2376,6 @@ public static DreamValue NativeProc_splittext(NativeProc.Bundle bundle, DreamObj return new DreamValue(list); } - [DreamProc("sqrt")] - [DreamProcParameter("A", Type = DreamValueTypeFlag.Float)] - public static DreamValue NativeProc_sqrt(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - bundle.GetArgument(0, "A").TryGetValueAsFloat(out var a); - - return new DreamValue((float)Math.Sqrt(a)); - } - private static void OutputToStatPanel(DreamManager dreamManager, DreamConnection connection, DreamValue name, DreamValue value) { if (name.IsNull && value.TryGetValueAsDreamList(out var list)) { foreach (var item in list.GetValues()) @@ -2517,15 +2424,6 @@ public static DreamValue NativeProc_statpanel(NativeProc.Bundle bundle, DreamObj return DreamValue.False; } - [DreamProc("tan")] - [DreamProcParameter("X", Type = DreamValueTypeFlag.Float)] - public static DreamValue NativeProc_tan(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - bundle.GetArgument(0, "X").TryGetValueAsFloat(out var x); - double rad = x * (Math.PI / 180); - - return new DreamValue((float)Math.Tan(rad)); - } - [DreamProc("text2ascii")] [DreamProcParameter("T", Type = DreamValueTypeFlag.String)] [DreamProcParameter("pos", Type = DreamValueTypeFlag.Float, DefaultValue = 1)] From 009dba102bba653531e1a6d60cf51f096c1c1536 Mon Sep 17 00:00:00 2001 From: Penelope Haze Date: Tue, 10 Oct 2023 10:02:58 -0500 Subject: [PATCH 10/31] Fix compilation of nested lists in DMMs (#1481) --- OpenDreamRuntime/Objects/DreamObjectTree.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenDreamRuntime/Objects/DreamObjectTree.cs b/OpenDreamRuntime/Objects/DreamObjectTree.cs index 49cf208e69..bef8748be0 100644 --- a/OpenDreamRuntime/Objects/DreamObjectTree.cs +++ b/OpenDreamRuntime/Objects/DreamObjectTree.cs @@ -224,7 +224,8 @@ public DreamValue GetDreamValueFromJsonElement(object? value) { if (jsonElement.TryGetProperty("values", out JsonElement values)) { foreach (JsonElement listValue in values.EnumerateArray()) { - if (listValue.ValueKind == JsonValueKind.Object) { // key/value pair + if (listValue.ValueKind == JsonValueKind.Object && + !listValue.TryGetProperty("type", out _)) { if (!listValue.TryGetProperty("key", out var jsonKey) || !listValue.TryGetProperty("value", out var jsonValue)) throw new Exception("List value was missing a key or value property"); From 7a9d0110bc37eda10baaa28bc3621899c01c1ef9 Mon Sep 17 00:00:00 2001 From: Amy <3855802+amylizzle@users.noreply.github.com> Date: Wed, 11 Oct 2023 21:16:49 +0100 Subject: [PATCH 11/31] fix #warn choking on apostrophe (#1478) * fix #warn choking * cleaner * restore lexer --- .../Preprocessor/Pragma/warning_quote.dm | 10 ++++++++ .../Compiler/DMPreprocessor/DMPreprocessor.cs | 15 ++---------- .../DMPreprocessor/DMPreprocessorLexer.cs | 24 +++++++++++++++++-- 3 files changed, 34 insertions(+), 15 deletions(-) create mode 100644 Content.Tests/DMProject/Tests/Preprocessor/Pragma/warning_quote.dm diff --git a/Content.Tests/DMProject/Tests/Preprocessor/Pragma/warning_quote.dm b/Content.Tests/DMProject/Tests/Preprocessor/Pragma/warning_quote.dm new file mode 100644 index 0000000000..f8bdfd60be --- /dev/null +++ b/Content.Tests/DMProject/Tests/Preprocessor/Pragma/warning_quote.dm @@ -0,0 +1,10 @@ +#define CONDITION_TRUE + +#ifdef CONDITION_TRUE +#warn Oh no you're gonna have a bad time +#endif + +/proc/RunTest() + return + +#warn end of file \ No newline at end of file diff --git a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs index efb59a9a4d..f5c7c48782 100644 --- a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs +++ b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs @@ -582,21 +582,10 @@ private void HandleErrorOrWarningDirective(Token token) { if (!VerifyDirectiveUsage(token)) return; - StringBuilder messageBuilder = new StringBuilder(); - - Token messageToken = GetNextToken(true); - while (messageToken.Type != TokenType.EndOfFile) { - if (messageToken.Type == TokenType.Newline) break; - - messageBuilder.Append(messageToken.Text); - messageToken = GetNextToken(); - } - - string message = messageBuilder.ToString(); if (token.Type == TokenType.DM_Preproc_Error) { - DMCompiler.Emit(WarningCode.ErrorDirective, token.Location, message); + DMCompiler.Emit(WarningCode.ErrorDirective, token.Location, token.Text); } else { - DMCompiler.Emit(WarningCode.WarningDirective, token.Location, message); + DMCompiler.Emit(WarningCode.WarningDirective, token.Location, token.Text); } } diff --git a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs index 6cd410bcf8..d4f4785748 100644 --- a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs +++ b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs @@ -428,7 +428,28 @@ protected override Token ParseNextToken() { private bool TryMacroKeyword(string text, out Token token) { switch (text) { case "warn": - case "warning": token = CreateToken(TokenType.DM_Preproc_Warning, "#warn"); break; + case "warning": { + StringBuilder message = new StringBuilder(); + + while (GetCurrent() is not '\0' and not '\n') { + message.Append(GetCurrent()); + Advance(); + } + + token = CreateToken(TokenType.DM_Preproc_Warning, "#warn" + message.ToString()); + break; + } + case "error": { + StringBuilder message = new StringBuilder(); + + while (GetCurrent() is not '\0' and not '\n') { + message.Append(GetCurrent()); + Advance(); + } + + token = CreateToken(TokenType.DM_Preproc_Error, "#error" + message.ToString()); + break; + } case "include": token = CreateToken(TokenType.DM_Preproc_Include, "#include"); break; case "define": token = CreateToken(TokenType.DM_Preproc_Define, "#define"); break; case "undef": token = CreateToken(TokenType.DM_Preproc_Undefine, "#undef"); break; @@ -438,7 +459,6 @@ private bool TryMacroKeyword(string text, out Token token) { case "elif": token = CreateToken(TokenType.DM_Preproc_Elif, "#elif"); break; case "else": token = CreateToken(TokenType.DM_Preproc_Else, "#else"); break; case "endif": token = CreateToken(TokenType.DM_Preproc_EndIf, "#endif"); break; - case "error": token = CreateToken(TokenType.DM_Preproc_Error, "#error"); break; //OD-specific directives case "pragma": token = CreateToken(TokenType.DM_Preproc_Pragma, "#pragma"); break; default: From b7bb71e4f4b078d897ba17f12aec369856b78804 Mon Sep 17 00:00:00 2001 From: wixoa Date: Wed, 11 Oct 2023 20:52:17 -0400 Subject: [PATCH 12/31] Update RT to v165.0.0 (#1482) --- .../Interface/Controls/ControlChild.cs | 7 ++- .../Rendering/DMISpriteComponent.cs | 63 +++++++++---------- RobustToolbox | 2 +- 3 files changed, 35 insertions(+), 37 deletions(-) diff --git a/OpenDreamClient/Interface/Controls/ControlChild.cs b/OpenDreamClient/Interface/Controls/ControlChild.cs index d6cc930e23..7afe4d9891 100644 --- a/OpenDreamClient/Interface/Controls/ControlChild.cs +++ b/OpenDreamClient/Interface/Controls/ControlChild.cs @@ -64,10 +64,11 @@ protected override void UpdateElementDescriptor() { _grid.Children.Add(_rightElement); } + if(_leftElement is not null) - _leftElement.SetPositionFirst(); - if(_rightElement is not null) - _rightElement.SetPositionLast(); + _leftElement.SetPositionInParent(0); + if (_rightElement is not null) + _rightElement.SetPositionInParent(1); UpdateGrid(); } diff --git a/OpenDreamClient/Rendering/DMISpriteComponent.cs b/OpenDreamClient/Rendering/DMISpriteComponent.cs index 883e283192..fede2a08c7 100644 --- a/OpenDreamClient/Rendering/DMISpriteComponent.cs +++ b/OpenDreamClient/Rendering/DMISpriteComponent.cs @@ -1,43 +1,40 @@ using OpenDreamShared.Dream; using OpenDreamShared.Rendering; -using Robust.Client.GameObjects; -using Robust.Shared.Map; - -namespace OpenDreamClient.Rendering { - [RegisterComponent] - [ComponentReference(typeof(SharedDMISpriteComponent))] - internal sealed partial class DMISpriteComponent : SharedDMISpriteComponent { - [ViewVariables] public DreamIcon Icon { get; set; } = new DreamIcon(); - [ViewVariables] public ScreenLocation? ScreenLocation { get; set; } - - [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly IEntitySystemManager _entitySystemMan = default!; - private EntityLookupSystem? _lookupSystem; - - public DMISpriteComponent() { - Icon.SizeChanged += OnIconSizeChanged; - } - public bool IsVisible(bool checkWorld = true, int seeInvis = 0) { - if (Icon.Appearance?.Invisibility > seeInvis) return false; +namespace OpenDreamClient.Rendering; - if (checkWorld) { - //Only render movables not inside another movable's contents (parented to the grid) - //TODO: Use RobustToolbox's container system/components? - if (!_entityManager.TryGetComponent(Owner, out var transform)) - return false; +[RegisterComponent] +internal sealed partial class DMISpriteComponent : SharedDMISpriteComponent { + [ViewVariables] public DreamIcon Icon { get; set; } = new DreamIcon(); + [ViewVariables] public ScreenLocation? ScreenLocation { get; set; } - if (transform.ParentUid != transform.GridUid) - return false; - } + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IEntitySystemManager _entitySystemMan = default!; + private EntityLookupSystem? _lookupSystem; - return true; - } + public DMISpriteComponent() { + Icon.SizeChanged += OnIconSizeChanged; + } + + public bool IsVisible(bool checkWorld = true, int seeInvis = 0) { + if (Icon.Appearance?.Invisibility > seeInvis) return false; - private void OnIconSizeChanged() { - _entityManager.TryGetComponent(Owner, out var transform); - _lookupSystem ??= _entitySystemMan.GetEntitySystem(); - _lookupSystem?.FindAndAddToEntityTree(Owner, xform: transform); + if (checkWorld) { + //Only render movables not inside another movable's contents (parented to the grid) + //TODO: Use RobustToolbox's container system/components? + if (!_entityManager.TryGetComponent(Owner, out var transform)) + return false; + + if (transform.ParentUid != transform.GridUid) + return false; } + + return true; + } + + private void OnIconSizeChanged() { + _entityManager.TryGetComponent(Owner, out var transform); + _lookupSystem ??= _entitySystemMan.GetEntitySystem(); + _lookupSystem?.FindAndAddToEntityTree(Owner, xform: transform); } } diff --git a/RobustToolbox b/RobustToolbox index 8ce3a03136..a8ddd837c8 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 8ce3a03136e1f334772c3b60040923d0037bced8 +Subproject commit a8ddd837c847f7cd8e71ae4926418a992b6ff4a3 From c7e6b07d55bd40c7113feab631f1ffd1811425fb Mon Sep 17 00:00:00 2001 From: wixoa Date: Wed, 11 Oct 2023 21:08:19 -0400 Subject: [PATCH 13/31] Add /world/Topic() handling (#1485) * add topic/export support * remove unimpl marker * reformat * Apply suggestions from code review Co-authored-by: wixoa * move to dedicated proc dont await consuming it * format * Separate game port and Topic port * Don't include `?` in `T` arg * Set default port back to 25567 --------- Co-authored-by: ZephyrTFA Co-authored-by: Zephyr <12817816+ZephyrTFA@users.noreply.github.com> --- DMCompiler/DMStandard/Defines.dm | 1 + DMCompiler/DMStandard/Types/World.dm | 11 +- OpenDreamRuntime/DreamManager.Connections.cs | 135 +++++++++++++++++- OpenDreamRuntime/DreamManager.cs | 3 + .../Objects/Types/DreamObjectWorld.cs | 7 +- OpenDreamRuntime/OpenDreamRuntime.csproj | 3 + .../Procs/Native/DreamProcNativeWorld.cs | 36 ++++- OpenDreamServer/server_config.toml | 2 + OpenDreamShared/OpenDreamCVars.cs | 3 + 9 files changed, 192 insertions(+), 9 deletions(-) diff --git a/DMCompiler/DMStandard/Defines.dm b/DMCompiler/DMStandard/Defines.dm index acf07008f0..c32d2e50ea 100644 --- a/DMCompiler/DMStandard/Defines.dm +++ b/DMCompiler/DMStandard/Defines.dm @@ -2,6 +2,7 @@ #define FALSE 0 #define OPENDREAM 1 +#define OPENDREAM_TOPIC_PORT_EXISTS 1 // Remove this if world.opendream_topic_port is ever removed #define NORTH 1 #define SOUTH 2 diff --git a/DMCompiler/DMStandard/Types/World.dm b/DMCompiler/DMStandard/Types/World.dm index b5242483ab..ce8687057e 100644 --- a/DMCompiler/DMStandard/Types/World.dm +++ b/DMCompiler/DMStandard/Types/World.dm @@ -44,9 +44,6 @@ var/system_type - proc/New() - proc/Del() - var/map_cpu = 0 as opendream_unimplemented var/hub as opendream_unimplemented var/hub_password as opendream_unimplemented @@ -56,6 +53,13 @@ var/map_format = TOPDOWN_MAP as opendream_unimplemented var/cache_lifespan = 30 as opendream_unimplemented var/executor as opendream_unimplemented + + // An OpenDream read-only var that tells you what port Topic() is listening on + // Remove OPENDREAM_TOPIC_PORT_EXISTS if this is ever removed + var/const/opendream_topic_port + + proc/New() + proc/Del() proc/Profile(command, type, format) set opendream_unimplemented = TRUE @@ -82,7 +86,6 @@ proc/Import() set opendream_unimplemented = TRUE proc/Topic(T,Addr,Master,Keys) - set opendream_unimplemented = TRUE proc/SetScores() set opendream_unimplemented = TRUE diff --git a/OpenDreamRuntime/DreamManager.Connections.cs b/OpenDreamRuntime/DreamManager.Connections.cs index 29de2dfac4..1201c49582 100644 --- a/OpenDreamRuntime/DreamManager.Connections.cs +++ b/OpenDreamRuntime/DreamManager.Connections.cs @@ -1,16 +1,33 @@ +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using OpenDreamShared; using OpenDreamShared.Network.Messages; using Robust.Server.Player; +using Robust.Shared.Configuration; using Robust.Shared.Enums; using Robust.Shared.Network; namespace OpenDreamRuntime { public sealed partial class DreamManager { + private static readonly byte[] ByondTopicHeaderRaw = { 0x00, 0x83 }; + private static readonly byte[] ByondTopicHeaderEncrypted = { 0x00, 0x15 }; + [Dependency] private readonly IServerNetManager _netManager = default!; + [Dependency] private readonly IConfigurationManager _config = default!; - private readonly Dictionary _connections = new(); + private readonly Dictionary _connections = new Dictionary(); public IEnumerable Connections => _connections.Values; + private Socket? _worldTopicSocket; + + private Task? _worldTopicListener; + private CancellationTokenSource? _worldTopicCancellationToken; + private void InitializeConnectionManager() { _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; @@ -34,6 +51,120 @@ private void InitializeConnectionManager() { _netManager.RegisterNetMessage(RxAckLoadInterface); _netManager.RegisterNetMessage(); _netManager.RegisterNetMessage(); + + var worldTopicAddress = new IPEndPoint(IPAddress.Loopback, _config.GetCVar(OpenDreamCVars.TopicPort)); + _sawmill.Debug($"Binding World Topic at {worldTopicAddress}"); + _worldTopicSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) { + ReceiveTimeout = 5000, + SendTimeout = 5000, + ExclusiveAddressUse = false, + }; + _worldTopicSocket.Bind(worldTopicAddress); + _worldTopicSocket.Listen(); + _worldTopicCancellationToken = new CancellationTokenSource(); + _worldTopicListener = WorldTopicListener(_worldTopicCancellationToken.Token); + } + + private void ShutdownConnectionManager() { + _worldTopicSocket!.Dispose(); + _worldTopicCancellationToken!.Cancel(); + } + + private async Task ConsumeAndHandleWorldTopicSocket(Socket remote, CancellationToken cancellationToken) { + try { + async Task ParseByondTopic(Socket from) { + var buffer = new byte[2]; + await from.ReceiveAsync(buffer, cancellationToken); + if (!buffer.SequenceEqual(ByondTopicHeaderRaw)) { + if (buffer.SequenceEqual(ByondTopicHeaderEncrypted)) + _sawmill.Warning("Encrypted World Topic request is not implemented."); + return null; + } + + await from.ReceiveAsync(buffer, cancellationToken); + if (BitConverter.IsLittleEndian) + buffer = buffer.Reverse().ToArray(); + var length = BitConverter.ToUInt16(buffer); + + buffer = new byte[length]; + var read = await from.ReceiveAsync(buffer, cancellationToken); + if (read != buffer.Length) { + _sawmill.Warning("failed to parse byond topic due to insufficient data read"); + return null; + } + + return Encoding.ASCII.GetString(buffer[6..^1]); + } + + var topic = await ParseByondTopic(remote); + if (topic is null) { + return; + } + + var remoteAddress = (remote.RemoteEndPoint as IPEndPoint)!.Address.ToString(); + _sawmill.Debug($"World Topic: '{remoteAddress}' -> '{topic}'"); + var topicResponse = WorldInstance.SpawnProc("Topic", null, new DreamValue(topic), new DreamValue(remoteAddress)); + if (topicResponse.IsNull) { + return; + } + + byte[] responseData; + byte responseType; + switch (topicResponse.Type) { + case DreamValue.DreamValueType.Float: + responseType = 0x2a; + responseData = BitConverter.GetBytes(topicResponse.MustGetValueAsFloat()); + break; + + case DreamValue.DreamValueType.String: + responseType = 0x06; + responseData = Encoding.ASCII.GetBytes(topicResponse.MustGetValueAsString().Replace("\0", "")).Append((byte)0x00).ToArray(); + break; + + case DreamValue.DreamValueType.DreamResource: + case DreamValue.DreamValueType.DreamObject: + case DreamValue.DreamValueType.DreamType: + case DreamValue.DreamValueType.DreamProc: + case DreamValue.DreamValueType.Appearance: + case DreamValue.DreamValueType.ProcStub: + case DreamValue.DreamValueType.VerbStub: + default: + _sawmill.Warning($"Unimplemented /world/Topic response type: {topicResponse.Type}"); + return; + } + + var totalLength = (ushort)(responseData.Length + 1); + var lengthData = BitConverter.GetBytes(totalLength); + if (BitConverter.IsLittleEndian) + lengthData = lengthData.Reverse().ToArray(); + + var responseBuffer = new List(ByondTopicHeaderRaw); + responseBuffer.AddRange(lengthData); + responseBuffer.Add(responseType); + responseBuffer.AddRange(responseData); + var responseActual = responseBuffer.ToArray(); + + var sent = await remote.SendAsync(responseActual, cancellationToken); + if (sent != responseActual.Length) + _sawmill.Warning("Failed to reply to /world/Topic: response buffer not fully sent"); + + } + finally { + await remote.DisconnectAsync(false, cancellationToken); + } + } + + private async Task WorldTopicListener(CancellationToken cancellationToken) { + if (_worldTopicSocket is null) + throw new InvalidOperationException("Attempted to start the World Topic Listener without a valid socket bind address."); + + while (!cancellationToken.IsCancellationRequested) { + var pending = await _worldTopicSocket.AcceptAsync(cancellationToken); + _ = ConsumeAndHandleWorldTopicSocket(pending, cancellationToken); + } + + _worldTopicSocket!.Dispose(); + _worldTopicSocket = null!; } private void RxSelectStatPanel(MsgSelectStatPanel message) { @@ -74,6 +205,7 @@ private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) { e.Session.ConnectedClient.SendMessage(msgLoadInterface); break; + case SessionStatus.InGame: { if (!_connections.TryGetValue(e.Session.UserId, out var connection)) { connection = new DreamConnection(); @@ -84,6 +216,7 @@ private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) { connection.HandleConnection(e.Session); break; } + case SessionStatus.Disconnected: { if (_connections.TryGetValue(e.Session.UserId, out var connection)) connection.HandleDisconnection(); diff --git a/OpenDreamRuntime/DreamManager.cs b/OpenDreamRuntime/DreamManager.cs index b82840d9e9..b612089ee8 100644 --- a/OpenDreamRuntime/DreamManager.cs +++ b/OpenDreamRuntime/DreamManager.cs @@ -85,6 +85,9 @@ public void StartWorld() { } public void Shutdown() { + // TODO: Respect not calling parent and aborting shutdown + WorldInstance.Delete(); + ShutdownConnectionManager(); Initialized = false; } diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs b/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs index 700de00425..a664e6b51d 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs @@ -125,7 +125,7 @@ protected override bool TryGetVar(string varName, out DreamValue value) { case "timeofday": value = new DreamValue((int)DateTime.UtcNow.TimeOfDay.TotalMilliseconds / 100); return true; - + case "timezone": value = new DreamValue((int)DateTimeOffset.Now.Offset.TotalHours); return true; @@ -212,6 +212,11 @@ protected override bool TryGetVar(string varName, out DreamValue value) { return true; + // Remove OPENDREAM_TOPIC_PORT_EXISTS if this is ever removed + case "opendream_topic_port": + value = new(_cfg.GetCVar(OpenDreamCVars.TopicPort)); + return true; + default: // Note that invalid vars on /world will give null and not error in BYOND // We don't replicate that diff --git a/OpenDreamRuntime/OpenDreamRuntime.csproj b/OpenDreamRuntime/OpenDreamRuntime.csproj index 895aae9607..0b57032d72 100644 --- a/OpenDreamRuntime/OpenDreamRuntime.csproj +++ b/OpenDreamRuntime/OpenDreamRuntime.csproj @@ -12,6 +12,9 @@ + + + diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeWorld.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeWorld.cs index 6e0ab95dd1..88453dc29f 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeWorld.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeWorld.cs @@ -1,6 +1,8 @@ -using System.Linq; +using System.IO; +using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using Byond.TopicSender; using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.Types; using Robust.Server; @@ -18,8 +20,36 @@ public static async Task NativeProc_Export(AsyncNativeProc.State sta if (!Uri.TryCreate(addr, UriKind.RelativeOrAbsolute, out var uri)) throw new ArgumentException("Unable to parse URI."); - if (uri.Scheme is not ("http" or "https")) - throw new NotSupportedException("non-HTTP world.Export is not supported."); + if (uri.Scheme is not ("http" or "https" or "byond")) + throw new NotSupportedException($"Unknown scheme for world.Export: '{uri.Scheme}'"); + + if (uri.Scheme is "byond") { + var tenSecondTimeout = TimeSpan.FromSeconds(10); + var topicClient = new TopicClient(new SocketParameters { + ConnectTimeout = tenSecondTimeout, + DisconnectTimeout = tenSecondTimeout, + ReceiveTimeout = tenSecondTimeout, + SendTimeout = tenSecondTimeout, + }); + + var topicResponse = await topicClient.SendTopic(uri.Host, uri.Query[1..], Convert.ToUInt16(uri.Port)); + switch (topicResponse.ResponseType) { + case TopicResponseType.FloatResponse: + return new DreamValue(topicResponse.FloatData!.Value); + + case TopicResponseType.StringResponse: + return new DreamValue(topicResponse.StringData!); + + case TopicResponseType.UnknownResponse: + var byteList = state.ObjectTree.CreateList(); + foreach (var @byte in topicResponse.RawData) + byteList.AddValue(new DreamValue(@byte)); + return new DreamValue(byteList); + + default: + throw new IOException($"Topic returned an unknown response type: '{topicResponse.ResponseType}'"); + } + } // TODO: Maybe cache HttpClient. var client = new HttpClient(); diff --git a/OpenDreamServer/server_config.toml b/OpenDreamServer/server_config.toml index 1a466fe2d4..1cc7f89338 100644 --- a/OpenDreamServer/server_config.toml +++ b/OpenDreamServer/server_config.toml @@ -44,6 +44,8 @@ welcomemsg = "Welcome to the server!" json_path = "" # If true, exceptions will always print to the console in addition to the world.log file (if set) always_show_exceptions = false +# The port used to listen for /world/Topic() calls +topic_port = 25567 # These are actually client cvars that, currently, have to be passed as program args. # Documented here in hopes that we'll have a client_config.toml without using the launcher one day diff --git a/OpenDreamShared/OpenDreamCVars.cs b/OpenDreamShared/OpenDreamCVars.cs index 1487ee2c46..203955a6cc 100644 --- a/OpenDreamShared/OpenDreamCVars.cs +++ b/OpenDreamShared/OpenDreamCVars.cs @@ -21,5 +21,8 @@ public abstract class OpenDreamCVars { public static readonly CVarDef WorldParams = CVarDef.Create("opendream.world_params", string.Empty, CVar.SERVERONLY); + + public static readonly CVarDef TopicPort = + CVarDef.Create("opendream.topic_port", 25567, CVar.SERVERONLY); } } From 5c1e907228d11878e57d450db0cde3c8ee142a10 Mon Sep 17 00:00:00 2001 From: wixoa Date: Thu, 12 Oct 2023 20:22:47 -0400 Subject: [PATCH 14/31] Move many math operations to `SharedOperations.cs` (#1477) As well as error improvements --- DMCompiler/Bytecode/DreamProcOpcode.cs | 8 +- DMCompiler/DM/DMProc.cs | 16 +- DMCompiler/DM/Expressions/Builtins.cs | 216 ++++++++++++------ DMCompiler/DM/Expressions/Constant.cs | 107 --------- DMCompiler/DM/Visitors/DMVisitorExpression.cs | 8 +- DMCompiler/DMStandard/DefaultPragmaConfig.dm | 1 + DMCompiler/SharedOperations.cs | 65 ++++++ OpenDream.sln.DotSettings | 3 + OpenDreamRuntime/Procs/DMOpcodeHandlers.cs | 69 +++--- OpenDreamRuntime/Procs/DMProc.cs | 8 +- OpenDreamShared/Compiler/CompilerError.cs | 1 + 11 files changed, 281 insertions(+), 221 deletions(-) create mode 100644 DMCompiler/SharedOperations.cs diff --git a/DMCompiler/Bytecode/DreamProcOpcode.cs b/DMCompiler/Bytecode/DreamProcOpcode.cs index 53ec087bba..17c8a7a973 100644 --- a/DMCompiler/Bytecode/DreamProcOpcode.cs +++ b/DMCompiler/Bytecode/DreamProcOpcode.cs @@ -131,10 +131,10 @@ public enum DreamProcOpcode : byte { Sin = 0x79, Cos = 0x7A, Tan = 0x7B, - Arcsin = 0x7C, - Arccos = 0x7D, - Arctan = 0x7E, - Arctan2 = 0x7F, + ArcSin = 0x7C, + ArcCos = 0x7D, + ArcTan = 0x7E, + ArcTan2 = 0x7F, Sqrt = 0x80, Log = 0x81, LogE = 0x82, diff --git a/DMCompiler/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs index 5c4a58f996..8ba88da617 100644 --- a/DMCompiler/DM/DMProc.cs +++ b/DMCompiler/DM/DMProc.cs @@ -957,21 +957,21 @@ public void Tan() { WriteOpcode(DreamProcOpcode.Tan); } - public void Arcsin() { - WriteOpcode(DreamProcOpcode.Arcsin); + public void ArcSin() { + WriteOpcode(DreamProcOpcode.ArcSin); } - public void Arccos() { - WriteOpcode(DreamProcOpcode.Arccos); + public void ArcCos() { + WriteOpcode(DreamProcOpcode.ArcCos); } - public void Arctan() { - WriteOpcode(DreamProcOpcode.Arctan); + public void ArcTan() { + WriteOpcode(DreamProcOpcode.ArcTan); } - public void Arctan2() { + public void ArcTan2() { ShrinkStack(1); - WriteOpcode(DreamProcOpcode.Arctan2); + WriteOpcode(DreamProcOpcode.ArcTan2); } public void Sqrt() { diff --git a/DMCompiler/DM/Expressions/Builtins.cs b/DMCompiler/DM/Expressions/Builtins.cs index 280dd67e09..ab46a0fc31 100644 --- a/DMCompiler/DM/Expressions/Builtins.cs +++ b/DMCompiler/DM/Expressions/Builtins.cs @@ -3,6 +3,7 @@ using OpenDreamShared.Dream; using OpenDreamShared.Json; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using DMCompiler.Bytecode; namespace DMCompiler.DM.Expressions { @@ -644,20 +645,26 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { } } - class Sin : DMExpression { - DMExpression _expr; + internal class Sin : DMExpression { + private readonly DMExpression _expr; public Sin(Location location, DMExpression expr) : base(location) { _expr = expr; } - - public override bool TryAsConstant(out Constant constant) { + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { if (!_expr.TryAsConstant(out constant)) { constant = null; return false; } - constant = constant.Sin(); + if (constant is not Number {Value: var x}) { + x = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _expr.Location, + "Invalid value treated as 0, sin(0) will always be 0"); + } + + constant = new Number(Location, SharedOperations.Sin(x)); return true; } @@ -667,20 +674,26 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { } } - class Cos : DMExpression { - DMExpression _expr; + internal class Cos : DMExpression { + private readonly DMExpression _expr; public Cos(Location location, DMExpression expr) : base(location) { _expr = expr; } - - public override bool TryAsConstant(out Constant constant) { + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { if (!_expr.TryAsConstant(out constant)) { constant = null; return false; } - constant = constant.Cos(); + if (constant is not Number {Value: var x}) { + x = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _expr.Location, + "Invalid value treated as 0, cos(0) will always be 1"); + } + + constant = new Number(Location, SharedOperations.Cos(x)); return true; } @@ -690,20 +703,26 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { } } - class Tan : DMExpression { - DMExpression _expr; + internal class Tan : DMExpression { + private readonly DMExpression _expr; public Tan(Location location, DMExpression expr) : base(location) { _expr = expr; } - - public override bool TryAsConstant(out Constant constant) { + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { if (!_expr.TryAsConstant(out constant)) { constant = null; return false; } - constant = constant.Tan(); + if (constant is not Number {Value: var x}) { + x = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _expr.Location, + "Invalid value treated as 0, tan(0) will always be 0"); + } + + constant = new Number(Location, SharedOperations.Tan(x)); return true; } @@ -713,115 +732,164 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { } } - class Arcsin : DMExpression { - DMExpression _expr; + internal class ArcSin : DMExpression { + private readonly DMExpression _expr; - public Arcsin(Location location, DMExpression expr) : base(location) { + public ArcSin(Location location, DMExpression expr) : base(location) { _expr = expr; } - - public override bool TryAsConstant(out Constant constant) { + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { if (!_expr.TryAsConstant(out constant)) { constant = null; return false; } - constant = constant.Arcsin(); + if (constant is not Number {Value: var x}) { + x = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _expr.Location, + "Invalid value treated as 0, arcsin(0) will always be 0"); + } + + if (x is < -1 or > 1) { + DMCompiler.Emit(WarningCode.BadArgument, _expr.Location, $"Invalid value {x}, must be >= -1 and <= 1"); + x = 0; + } + + constant = new Number(Location, SharedOperations.ArcSin(x)); return true; } public override void EmitPushValue(DMObject dmObject, DMProc proc) { _expr.EmitPushValue(dmObject, proc); - proc.Arcsin(); + proc.ArcSin(); } } - class Arccos : DMExpression { - DMExpression _expr; + internal class ArcCos : DMExpression { + private readonly DMExpression _expr; - public Arccos(Location location, DMExpression expr) : base(location) { + public ArcCos(Location location, DMExpression expr) : base(location) { _expr = expr; } - - public override bool TryAsConstant(out Constant constant) { + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { if (!_expr.TryAsConstant(out constant)) { constant = null; return false; } - constant = constant.Arccos(); + if (constant is not Number {Value: var x}) { + x = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _expr.Location, + "Invalid value treated as 0, arccos(0) will always be 1"); + } + + if (x is < -1 or > 1) { + DMCompiler.Emit(WarningCode.BadArgument, _expr.Location, $"Invalid value {x}, must be >= -1 and <= 1"); + x = 0; + } + + constant = new Number(Location, SharedOperations.ArcCos(x)); return true; } public override void EmitPushValue(DMObject dmObject, DMProc proc) { _expr.EmitPushValue(dmObject, proc); - proc.Arccos(); + proc.ArcCos(); } } - class Arctan : DMExpression { - DMExpression _expr; + internal class ArcTan : DMExpression { + private readonly DMExpression _expr; - public Arctan(Location location, DMExpression expr) : base(location) { + public ArcTan(Location location, DMExpression expr) : base(location) { _expr = expr; } - - public override bool TryAsConstant(out Constant constant) { + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { if (!_expr.TryAsConstant(out constant)) { constant = null; return false; } - constant = constant.Arctan(); + if (constant is not Number {Value: var a}) { + a = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _expr.Location, + "Invalid value treated as 0, arctan(0) will always be 0"); + } + + constant = new Number(Location, SharedOperations.ArcTan(a)); return true; } public override void EmitPushValue(DMObject dmObject, DMProc proc) { _expr.EmitPushValue(dmObject, proc); - proc.Arctan(); + proc.ArcTan(); } } - class Arctan2 : DMExpression { - DMExpression _xexpr; - DMExpression _yexpr; + internal class ArcTan2 : DMExpression { + private readonly DMExpression _xExpr; + private readonly DMExpression _yExpr; - public Arctan2(Location location, DMExpression xexpr, DMExpression yexpr) : base(location) { - _xexpr = xexpr; - _yexpr = yexpr; + public ArcTan2(Location location, DMExpression xExpr, DMExpression yExpr) : base(location) { + _xExpr = xExpr; + _yExpr = yExpr; } - - public override bool TryAsConstant(out Constant constant) { - if (!_xexpr.TryAsConstant(out Constant xconst) || !_yexpr.TryAsConstant(out Constant yconst)) { + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { + if (!_xExpr.TryAsConstant(out var xConst) || !_yExpr.TryAsConstant(out var yConst)) { constant = null; return false; } - constant = xconst.Arctan2(yconst); + if (xConst is not Number {Value: var x}) { + x = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _xExpr.Location, "Invalid x value treated as 0"); + } + + if (yConst is not Number {Value: var y}) { + y = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _xExpr.Location, "Invalid y value treated as 0"); + } + + constant = new Number(Location, SharedOperations.ArcTan(x, y)); return true; } public override void EmitPushValue(DMObject dmObject, DMProc proc) { - _xexpr.EmitPushValue(dmObject, proc); - _yexpr.EmitPushValue(dmObject, proc); - proc.Arctan2(); + _xExpr.EmitPushValue(dmObject, proc); + _yExpr.EmitPushValue(dmObject, proc); + proc.ArcTan2(); } } - class Sqrt : DMExpression { - DMExpression _expr; + internal class Sqrt : DMExpression { + private readonly DMExpression _expr; public Sqrt(Location location, DMExpression expr) : base(location) { _expr = expr; } - public override bool TryAsConstant(out Constant constant) { + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { if (!_expr.TryAsConstant(out constant)) { constant = null; return false; } - constant = constant.Sqrt(); + if (constant is not Number {Value: var a}) { + a = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _expr.Location, + "Invalid value treated as 0, sqrt(0) will always be 0"); + } + + if (a < 0) { + DMCompiler.Emit(WarningCode.BadArgument, _expr.Location, + $"Cannot get the square root of a negative number ({a})"); + } + + constant = new Number(Location, SharedOperations.Sqrt(a)); return true; } @@ -831,32 +899,44 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { } } - class Log : DMExpression { - DMExpression _expr; - DMExpression? _baseExpr; + internal class Log : DMExpression { + private readonly DMExpression _expr; + private readonly DMExpression? _baseExpr; public Log(Location location, DMExpression expr, DMExpression? baseExpr) : base(location) { _expr = expr; _baseExpr = baseExpr; } - - public override bool TryAsConstant(out Constant constant) { + + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { if (!_expr.TryAsConstant(out constant)) { constant = null; return false; } + if (constant is not Number {Value: var value} || value <= 0) { + value = 1; + DMCompiler.Emit(WarningCode.BadArgument, _expr.Location, + "Invalid value, must be a number greater than 0"); + } + if (_baseExpr == null) { - constant = constant.Log(null); + constant = new Number(Location, SharedOperations.Log(value)); return true; } - if (!_baseExpr.TryAsConstant(out Constant baseConstant)) { + if (!_baseExpr.TryAsConstant(out var baseConstant)) { constant = null; return false; } - constant = constant.Log(baseConstant); + if (baseConstant is not Number {Value: var baseValue} || baseValue <= 0) { + baseValue = 10; + DMCompiler.Emit(WarningCode.BadArgument, _baseExpr.Location, + "Invalid base, must be a number greater than 0"); + } + + constant = new Number(Location, SharedOperations.Log(value, baseValue)); return true; } @@ -871,20 +951,26 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { } } - class Abs : DMExpression { - DMExpression _expr; + internal class Abs : DMExpression { + private readonly DMExpression _expr; public Abs(Location location, DMExpression expr) : base(location) { _expr = expr; } - public override bool TryAsConstant(out Constant constant) { + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { if (!_expr.TryAsConstant(out constant)) { constant = null; return false; } - constant = constant.Abs(); + if (constant is not Number {Value: var a}) { + a = 0; + DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, _expr.Location, + "Invalid value treated as 0, abs(0) will always be 0"); + } + + constant = new Number(Location, SharedOperations.Abs(a)); return true; } diff --git a/DMCompiler/DM/Expressions/Constant.cs b/DMCompiler/DM/Expressions/Constant.cs index f7d00a87ae..c8391830aa 100644 --- a/DMCompiler/DM/Expressions/Constant.cs +++ b/DMCompiler/DM/Expressions/Constant.cs @@ -97,49 +97,6 @@ public virtual Constant LessThan(Constant rhs) { public virtual Constant LessThanOrEqual(Constant rhs) { throw new CompileErrorException(Location, $"const operation \"{this} <= {rhs}\" is invalid"); } - - public virtual Constant Sin() { - throw new CompileErrorException(Location, $"const operation \"sin({this})\" is invalid"); - } - - public virtual Constant Cos() { - throw new CompileErrorException(Location, $"const operation \"cos({this})\" is invalid"); - } - - public virtual Constant Tan() { - throw new CompileErrorException(Location, $"const operation \"tan({this})\" is invalid"); - } - - public virtual Constant Arcsin() { - throw new CompileErrorException(Location, $"const operation \"arcsin({this})\" is invalid"); - } - - public virtual Constant Arccos() { - throw new CompileErrorException(Location, $"const operation \"arccos({this})\" is invalid"); - } - - public virtual Constant Arctan() { - throw new CompileErrorException(Location, $"const operation \"arctan({this})\" is invalid"); - } - - public virtual Constant Arctan2(Constant yConst) { - throw new CompileErrorException(Location, $"const operation \"arctan({this}, {yConst})\" is invalid"); - } - - public virtual Constant Sqrt() { - throw new CompileErrorException(Location, $"const operation \"sqrt({this})\" is invalid"); - } - - public virtual Constant Log(Constant? baseVal) { - if (baseVal == null) { - throw new CompileErrorException(Location, $"const operation \"log({this})\" is invalid"); - } - throw new CompileErrorException(Location, $"const operation \"log({baseVal}, {this})\" is invalid"); - } - - public virtual Constant Abs() { - throw new CompileErrorException(Location, $"const operation \"abs({this})\" is invalid"); - } #endregion } @@ -359,70 +316,6 @@ public override Constant LessThanOrEqual(Constant rhs) { } return new Number(Location, (Value <= rhsNum.Value) ? 1 : 0); } - - public override Constant Sin() { - return new Number(Location, MathF.Sin(Value / 180 * MathF.PI)); - } - - public override Constant Cos() { - return new Number(Location, MathF.Cos(Value / 180 * MathF.PI)); - } - - public override Constant Tan() { - return new Number(Location, MathF.Tan(Value / 180 * MathF.PI)); - } - - public override Constant Arcsin() { - if (Value < -1 || Value > 1) { - throw new CompileErrorException(Location, $"const operation \"arcsin({this})\" is invalid (out of range)"); - } - return new Number(Location, MathF.Asin(Value) / MathF.PI * 180); - } - - public override Constant Arccos() { - if (Value < -1 || Value > 1) { - throw new CompileErrorException(Location, $"const operation \"arccos({this})\" is invalid (out of range)"); - } - return new Number(Location, MathF.Acos(Value) / MathF.PI * 180); - } - - public override Constant Arctan() { - return new Number(Location, MathF.Atan(Value) / MathF.PI * 180); - } - - public override Constant Arctan2(Constant yConst) { - if (yConst is not Number yNum) { - throw new CompileErrorException(Location, $"const operation \"arctan2({this}, {yConst})\" is invalid"); - } - return new Number(Location, MathF.Atan2(yNum.Value, Value) / MathF.PI * 180); - } - - public override Constant Sqrt() { - if (Value < 0) { - throw new CompileErrorException(Location, $"const operation \"sqrt({this})\" is invalid (negative)"); - } - return new Number(Location, MathF.Sqrt(Value)); - } - - public override Constant Log(Constant? baseVal) { - if (Value <= 0) { - throw new CompileErrorException(Location, $"const operation \"log({this})\" is invalid (non-positive)"); - } - if (baseVal == null) { - return new Number(Location, MathF.Log(Value)); - } - if (baseVal is not Number baseNum) { - throw new CompileErrorException(Location, $"const operation \"log({this}, {baseVal})\" is invalid"); - } - if (baseNum.Value <= 0) { - throw new CompileErrorException(Location, $"const operation \"log({this}, {baseVal})\" is invalid (non-positive base)"); - } - return new Number(Location, MathF.Log(Value, baseNum.Value)); - } - - public override Constant Abs() { - return new Number(Location, MathF.Abs(Value)); - } } // "abc" diff --git a/DMCompiler/DM/Visitors/DMVisitorExpression.cs b/DMCompiler/DM/Visitors/DMVisitorExpression.cs index 9d711dc4c3..8acb35a0dc 100644 --- a/DMCompiler/DM/Visitors/DMVisitorExpression.cs +++ b/DMCompiler/DM/Visitors/DMVisitorExpression.cs @@ -996,23 +996,23 @@ public void VisitTan(DMASTTan tan) { public void VisitArcsin(DMASTArcsin arcsin) { var expr = DMExpression.Create(_dmObject, _proc, arcsin.Expression, _inferredPath); - Result = new Expressions.Arcsin(arcsin.Location, expr); + Result = new Expressions.ArcSin(arcsin.Location, expr); } public void VisitArccos(DMASTArccos arccos) { var expr = DMExpression.Create(_dmObject, _proc, arccos.Expression, _inferredPath); - Result = new Expressions.Arccos(arccos.Location, expr); + Result = new Expressions.ArcCos(arccos.Location, expr); } public void VisitArctan(DMASTArctan arctan) { var expr = DMExpression.Create(_dmObject, _proc, arctan.Expression, _inferredPath); - Result = new Expressions.Arctan(arctan.Location, expr); + Result = new Expressions.ArcTan(arctan.Location, expr); } public void VisitArctan2(DMASTArctan2 arctan2) { var xexpr = DMExpression.Create(_dmObject, _proc, arctan2.XExpression, _inferredPath); var yexpr = DMExpression.Create(_dmObject, _proc, arctan2.YExpression, _inferredPath); - Result = new Expressions.Arctan2(arctan2.Location, xexpr, yexpr); + Result = new Expressions.ArcTan2(arctan2.Location, xexpr, yexpr); } public void VisitSqrt(DMASTSqrt sqrt) { diff --git a/DMCompiler/DMStandard/DefaultPragmaConfig.dm b/DMCompiler/DMStandard/DefaultPragmaConfig.dm index 7526cc2396..2c71d6c4e4 100644 --- a/DMCompiler/DMStandard/DefaultPragmaConfig.dm +++ b/DMCompiler/DMStandard/DefaultPragmaConfig.dm @@ -19,6 +19,7 @@ #pragma PointlessParentCall warning #pragma PointlessBuiltinCall warning #pragma SuspiciousMatrixCall warning +#pragma FallbackBuiltinArgument warning #pragma MalformedRange warning #pragma InvalidRange error #pragma InvalidSetStatement error diff --git a/DMCompiler/SharedOperations.cs b/DMCompiler/SharedOperations.cs new file mode 100644 index 0000000000..a29482757c --- /dev/null +++ b/DMCompiler/SharedOperations.cs @@ -0,0 +1,65 @@ +using System; +using System.Runtime.CompilerServices; + +namespace DMCompiler; + +/// +/// A class containing operations used by both the compiler and the server. +/// Helps make sure things like sin() and cos() give the same result on both. +/// +public static class SharedOperations { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Sin(float x) { + return MathF.Sin(x / 180 * MathF.PI); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Cos(float x) { + return MathF.Cos(x / 180 * MathF.PI); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Tan(float x) { + return MathF.Tan(x / 180 * MathF.PI); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ArcSin(float x) { + return MathF.Asin(x) / MathF.PI * 180; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ArcCos(float x) { + return MathF.Acos(x) / MathF.PI * 180; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ArcTan(float a) { + return MathF.Atan(a) / MathF.PI * 180; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ArcTan(float x, float y) { + return MathF.Atan2(y, x) / MathF.PI * 180; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Sqrt(float a) { + return MathF.Sqrt(a); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Log(float y) { + return MathF.Log(y); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Log(float y, float baseValue) { + return MathF.Log(y, baseValue); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Abs(float a) { + return MathF.Abs(a); + } +} diff --git a/OpenDream.sln.DotSettings b/OpenDream.sln.DotSettings index 06cd921c58..b9158a88ff 100644 --- a/OpenDream.sln.DotSettings +++ b/OpenDream.sln.DotSettings @@ -9,6 +9,9 @@ RHS UI True + True + True + True True True True diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index 4bd131d69a..da20d51b32 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using DMCompiler; using DMCompiler.Bytecode; using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.Types; @@ -1619,82 +1620,92 @@ public static ProcStatus EndTry(DMProcState state) { } public static ProcStatus Sin(DMProcState state) { - DreamValue value = state.Pop(); + float x = state.Pop().UnsafeGetValueAsFloat(); + float result = SharedOperations.Sin(x); - state.Push(new DreamValue(MathF.Sin(value.UnsafeGetValueAsFloat() / 180 * MathF.PI))); + state.Push(new DreamValue(result)); return ProcStatus.Continue; } public static ProcStatus Cos(DMProcState state) { - DreamValue value = state.Pop(); + float x = state.Pop().UnsafeGetValueAsFloat(); + float result = SharedOperations.Cos(x); - state.Push(new DreamValue(MathF.Cos(value.UnsafeGetValueAsFloat() / 180 * MathF.PI))); + state.Push(new DreamValue(result)); return ProcStatus.Continue; } public static ProcStatus Tan(DMProcState state) { - DreamValue value = state.Pop(); + float x = state.Pop().UnsafeGetValueAsFloat(); + float result = SharedOperations.Tan(x); - state.Push(new DreamValue(MathF.Tan(value.UnsafeGetValueAsFloat() / 180 * MathF.PI))); + state.Push(new DreamValue(result)); return ProcStatus.Continue; } - public static ProcStatus Arcsin(DMProcState state) { - DreamValue value = state.Pop(); + public static ProcStatus ArcSin(DMProcState state) { + float x = state.Pop().UnsafeGetValueAsFloat(); + float result = SharedOperations.ArcSin(x); - state.Push(new DreamValue(MathF.Asin(value.UnsafeGetValueAsFloat()) / MathF.PI * 180)); + state.Push(new DreamValue(result)); return ProcStatus.Continue; } - public static ProcStatus Arccos(DMProcState state) { - DreamValue value = state.Pop(); + public static ProcStatus ArcCos(DMProcState state) { + float x = state.Pop().UnsafeGetValueAsFloat(); + float result = SharedOperations.ArcCos(x); - state.Push(new DreamValue(MathF.Acos(value.UnsafeGetValueAsFloat()) / MathF.PI * 180)); + state.Push(new DreamValue(result)); return ProcStatus.Continue; } - public static ProcStatus Arctan(DMProcState state) { - DreamValue value = state.Pop(); + public static ProcStatus ArcTan(DMProcState state) { + float a = state.Pop().UnsafeGetValueAsFloat(); + float result = SharedOperations.ArcTan(a); - state.Push(new DreamValue(MathF.Atan(value.UnsafeGetValueAsFloat()) / MathF.PI * 180)); + state.Push(new DreamValue(result)); return ProcStatus.Continue; } - public static ProcStatus Arctan2(DMProcState state) { - DreamValue y = state.Pop(); - DreamValue x = state.Pop(); - var yValue = y.UnsafeGetValueAsFloat(); + public static ProcStatus ArcTan2(DMProcState state) { + float y = state.Pop().UnsafeGetValueAsFloat(); + float x = state.Pop().UnsafeGetValueAsFloat(); + float result = SharedOperations.ArcTan(x, y); - state.Push(new DreamValue(MathF.Atan2(yValue, x.UnsafeGetValueAsFloat()) / MathF.PI * 180)); + state.Push(new DreamValue(result)); return ProcStatus.Continue; } public static ProcStatus Sqrt(DMProcState state) { - DreamValue value = state.Pop(); + float a = state.Pop().UnsafeGetValueAsFloat(); + float result = SharedOperations.Sqrt(a); - state.Push(new DreamValue(MathF.Sqrt(value.UnsafeGetValueAsFloat()))); + state.Push(new DreamValue(result)); return ProcStatus.Continue; } public static ProcStatus Log(DMProcState state) { - DreamValue baseValue = state.Pop(); - DreamValue value = state.Pop(); + float baseValue = state.Pop().UnsafeGetValueAsFloat(); + float value = state.Pop().UnsafeGetValueAsFloat(); + float result = SharedOperations.Log(value, baseValue); - state.Push(new DreamValue(MathF.Log(value.UnsafeGetValueAsFloat(), baseValue.UnsafeGetValueAsFloat()))); + state.Push(new DreamValue(result)); return ProcStatus.Continue; } public static ProcStatus LogE(DMProcState state) { - DreamValue value = state.Pop(); + float y = state.Pop().UnsafeGetValueAsFloat(); + float result = SharedOperations.Log(y); - state.Push(new DreamValue(MathF.Log(value.UnsafeGetValueAsFloat()))); + state.Push(new DreamValue(result)); return ProcStatus.Continue; } public static ProcStatus Abs(DMProcState state) { - DreamValue value = state.Pop(); + float a = state.Pop().UnsafeGetValueAsFloat(); + float result = SharedOperations.Abs(a); - state.Push(new DreamValue(MathF.Abs(value.UnsafeGetValueAsFloat()))); + state.Push(new DreamValue(result)); return ProcStatus.Continue; } diff --git a/OpenDreamRuntime/Procs/DMProc.cs b/OpenDreamRuntime/Procs/DMProc.cs index 37be0c73c8..ca8d51a094 100644 --- a/OpenDreamRuntime/Procs/DMProc.cs +++ b/OpenDreamRuntime/Procs/DMProc.cs @@ -288,10 +288,10 @@ public sealed class DMProcState : ProcState { {DreamProcOpcode.Sin, DMOpcodeHandlers.Sin}, {DreamProcOpcode.Cos, DMOpcodeHandlers.Cos}, {DreamProcOpcode.Tan, DMOpcodeHandlers.Tan}, - {DreamProcOpcode.Arcsin, DMOpcodeHandlers.Arcsin}, - {DreamProcOpcode.Arccos, DMOpcodeHandlers.Arccos}, - {DreamProcOpcode.Arctan, DMOpcodeHandlers.Arctan}, - {DreamProcOpcode.Arctan2, DMOpcodeHandlers.Arctan2}, + {DreamProcOpcode.ArcSin, DMOpcodeHandlers.ArcSin}, + {DreamProcOpcode.ArcCos, DMOpcodeHandlers.ArcCos}, + {DreamProcOpcode.ArcTan, DMOpcodeHandlers.ArcTan}, + {DreamProcOpcode.ArcTan2, DMOpcodeHandlers.ArcTan2}, {DreamProcOpcode.Sqrt, DMOpcodeHandlers.Sqrt}, {DreamProcOpcode.Log, DMOpcodeHandlers.Log}, {DreamProcOpcode.LogE, DMOpcodeHandlers.LogE}, diff --git a/OpenDreamShared/Compiler/CompilerError.cs b/OpenDreamShared/Compiler/CompilerError.cs index a6bb60e0e8..0c468ffd54 100644 --- a/OpenDreamShared/Compiler/CompilerError.cs +++ b/OpenDreamShared/Compiler/CompilerError.cs @@ -44,6 +44,7 @@ public enum WarningCode { PointlessParentCall = 2205, PointlessBuiltinCall = 2206, // For pointless calls to issaved() or initial() SuspiciousMatrixCall = 2207, // Calling matrix() with seemingly the wrong arguments + FallbackBuiltinArgument = 2208, // A builtin (sin(), cos(), etc) with an invalid/fallback argument MalformedRange = 2300, InvalidRange = 2301, InvalidSetStatement = 2302, From f7f90cecdf154eff1eb6e5cccefe3d4e3e3529a8 Mon Sep 17 00:00:00 2001 From: wixoa Date: Thu, 12 Oct 2023 20:52:17 -0400 Subject: [PATCH 15/31] More comprehensive addition operator testing (#1483) * Add basic operator testing framework * Fix things up some * Update Content.Tests/DMProject/Tests/Operators/Shared/operator_testing.dm --- .../DMProject/Tests/Math/Addition.dm | 3 - .../DMProject/Tests/Operators/Addition.dm | 131 ++++++++++++++++++ .../DMProject/Tests/Operators/Shared/file.txt | 1 + .../Operators/Shared/operator_testing.dm | 37 +++++ DMCompiler/Bytecode/DreamProcOpcode.cs | 4 +- DMCompiler/DM/DMProc.cs | 15 +- DMCompiler/DM/Expressions/Constant.cs | 19 ++- OpenDreamRuntime/DreamManager.Connections.cs | 2 - OpenDreamRuntime/DreamValue.cs | 56 +------- OpenDreamRuntime/Objects/DreamObjectTree.cs | 10 -- OpenDreamRuntime/Procs/DMOpcodeHandlers.cs | 56 +++----- OpenDreamRuntime/Procs/DMProc.cs | 2 - .../Procs/Native/DreamProcNativeRoot.cs | 13 +- OpenDreamRuntime/Procs/ProcDecoder.cs | 2 - OpenDreamShared/Json/DreamObjectJson.cs | 6 +- 15 files changed, 216 insertions(+), 141 deletions(-) delete mode 100644 Content.Tests/DMProject/Tests/Math/Addition.dm create mode 100644 Content.Tests/DMProject/Tests/Operators/Addition.dm create mode 100644 Content.Tests/DMProject/Tests/Operators/Shared/file.txt create mode 100644 Content.Tests/DMProject/Tests/Operators/Shared/operator_testing.dm diff --git a/Content.Tests/DMProject/Tests/Math/Addition.dm b/Content.Tests/DMProject/Tests/Math/Addition.dm deleted file mode 100644 index 9cd65b11c8..0000000000 --- a/Content.Tests/DMProject/Tests/Math/Addition.dm +++ /dev/null @@ -1,3 +0,0 @@ -/proc/RunTest() - var/add = 5 + 2 - ASSERT(add == 7) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/Operators/Addition.dm b/Content.Tests/DMProject/Tests/Operators/Addition.dm new file mode 100644 index 0000000000..fe1fd03560 --- /dev/null +++ b/Content.Tests/DMProject/Tests/Operators/Addition.dm @@ -0,0 +1,131 @@ +#include "Shared/operator_testing.dm" + +/proc/add(a, b) + return a + b + +/proc/RunTest() + var/list/expected = list( + 20, + "Error", + 10, + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "ABCABC", + "ABC", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "ABC/datum/proc", + "ABC/datum/verb", + 10, + "ABC", + null, + 'Shared/file.txt', + list("A"), + operator_test_object, + /datum, + /datum/proc/foo, + /datum/verb/bar, + /datum/proc, + /datum/verb, + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + list("A", 10), + list("A", "ABC"), + list("A", null), + list("A", 'Shared/file.txt'), + list("A", "A"), + list("A", operator_test_object), + list("A", /datum), + list("A", /datum/proc/foo), + list("A", /datum/verb/bar), + list("A", /datum/proc), + list("A", /datum/verb), + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "/datum/procABC", + "/datum/proc", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "/datum/proc/datum/proc", + "/datum/proc/datum/verb", + "Error", + "/datum/verbABC", + "/datum/verb", + "Error", + "Error", + "Error", + "Error", + "Error", + "Error", + "/datum/verb/datum/proc", + "/datum/verb/datum/verb" + ) + + test_operator(/proc/add, expected) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/Operators/Shared/file.txt b/Content.Tests/DMProject/Tests/Operators/Shared/file.txt new file mode 100644 index 0000000000..ff722d7d2f --- /dev/null +++ b/Content.Tests/DMProject/Tests/Operators/Shared/file.txt @@ -0,0 +1 @@ +Used in operator_testing.dm \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/Operators/Shared/operator_testing.dm b/Content.Tests/DMProject/Tests/Operators/Shared/operator_testing.dm new file mode 100644 index 0000000000..4cc869d16c --- /dev/null +++ b/Content.Tests/DMProject/Tests/Operators/Shared/operator_testing.dm @@ -0,0 +1,37 @@ +// IGNORE + +// A helper method for testing operators against many kinds of values + +/var/datum/operator_test_object = new /datum() + +/datum/proc/foo() +/datum/verb/bar() + +/proc/test_operator(var/operator_proc, var/list/expected) + var/list/values = list( + 10, + "ABC", + null, + 'file.txt', + list("A"), + operator_test_object, + /datum, + /datum/proc/foo, + /datum/verb/bar, + /datum/proc, + /datum/verb + ) + + var/i = 1 + for (var/a in values) + for (var/b in values) + var/expected_result = expected[i++] + var/result + + try + result = call(operator_proc)(a, b) + catch + result = "Error" + + if (result ~! expected_result) + CRASH("Expected [json_encode(expected_result)] for [json_encode(a)] and [json_encode(b)], instead got [json_encode(result)]") diff --git a/DMCompiler/Bytecode/DreamProcOpcode.cs b/DMCompiler/Bytecode/DreamProcOpcode.cs index 17c8a7a973..5c7a1599a8 100644 --- a/DMCompiler/Bytecode/DreamProcOpcode.cs +++ b/DMCompiler/Bytecode/DreamProcOpcode.cs @@ -105,8 +105,8 @@ public enum DreamProcOpcode : byte { PushGlobalVars = 0x5F, ModulusModulus = 0x60, ModulusModulusReference = 0x61, - PushProcStub = 0x62, - PushVerbStub = 0x63, + //0x62 + //0x63 JumpIfNull = 0x64, JumpIfNullNoPop = 0x65, JumpIfTrueReference = 0x66, diff --git a/DMCompiler/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs index 8ba88da617..4f7320cb1e 100644 --- a/DMCompiler/DM/DMProc.cs +++ b/DMCompiler/DM/DMProc.cs @@ -1021,25 +1021,12 @@ public void PushProc(int procId) { WriteInt(procId); } - public void PushProcStub(int typeId) { - GrowStack(1); - WriteOpcode(DreamProcOpcode.PushProcStub); - WriteInt(typeId); - } - - public void PushVerbStub(int typeId) { - GrowStack(1); - WriteOpcode(DreamProcOpcode.PushVerbStub); - WriteInt(typeId); - } - public void PushNull() { GrowStack(1); WriteOpcode(DreamProcOpcode.PushNull); } - public void PushGlobalVars() - { + public void PushGlobalVars() { GrowStack(1); WriteOpcode(DreamProcOpcode.PushGlobalVars); } diff --git a/DMCompiler/DM/Expressions/Constant.cs b/DMCompiler/DM/Expressions/Constant.cs index c8391830aa..824f7680c7 100644 --- a/DMCompiler/DM/Expressions/Constant.cs +++ b/DMCompiler/DM/Expressions/Constant.cs @@ -3,8 +3,8 @@ using OpenDreamShared.Json; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using JetBrains.Annotations; using System.IO; namespace DMCompiler.DM.Expressions { @@ -512,10 +512,11 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { proc.PushProc(pathInfo.Value.Id); break; case PathType.ProcStub: - proc.PushProcStub(pathInfo.Value.Id); - break; case PathType.VerbStub: - proc.PushVerbStub(pathInfo.Value.Id); + var type = DMObjectTree.AllObjects[pathInfo.Value.Id].Path.PathString; + + // /datum/proc and /datum/verb just compile down to strings lmao + proc.PushString($"{type}/{(pathInfo.Value.Type == PathType.ProcStub ? "proc" : "verb")}"); break; default: DMCompiler.ForcedError(Location, $"Invalid PathType {pathInfo.Value.Type}"); @@ -535,11 +536,17 @@ public override bool TryAsJsonRepresentation(out object? json) { return false; } + if (pathInfo.Value.Type is PathType.ProcStub or PathType.VerbStub) { + var type = DMObjectTree.AllObjects[pathInfo.Value.Id].Path.PathString; + + json = $"{type}/{(pathInfo.Value.Type == PathType.ProcStub ? "proc" : "verb")}"; + return true; + } + JsonVariableType jsonType = pathInfo.Value.Type switch { PathType.TypeReference => JsonVariableType.Type, PathType.ProcReference => JsonVariableType.Proc, - PathType.ProcStub => JsonVariableType.ProcStub, - PathType.VerbStub => JsonVariableType.VerbStub + _ => throw new UnreachableException() }; json = new Dictionary() { diff --git a/OpenDreamRuntime/DreamManager.Connections.cs b/OpenDreamRuntime/DreamManager.Connections.cs index 1201c49582..9162e6b159 100644 --- a/OpenDreamRuntime/DreamManager.Connections.cs +++ b/OpenDreamRuntime/DreamManager.Connections.cs @@ -126,8 +126,6 @@ private async Task ConsumeAndHandleWorldTopicSocket(Socket remote, CancellationT case DreamValue.DreamValueType.DreamType: case DreamValue.DreamValueType.DreamProc: case DreamValue.DreamValueType.Appearance: - case DreamValue.DreamValueType.ProcStub: - case DreamValue.DreamValueType.VerbStub: default: _sawmill.Warning($"Unimplemented /world/Topic response type: {topicResponse.Type}"); return; diff --git a/OpenDreamRuntime/DreamValue.cs b/OpenDreamRuntime/DreamValue.cs index 7b78e4e313..e179338265 100644 --- a/OpenDreamRuntime/DreamValue.cs +++ b/OpenDreamRuntime/DreamValue.cs @@ -27,11 +27,7 @@ public enum DreamValueType { DreamObject = 4, DreamType = 5, DreamProc = 6, - Appearance = 7, - - // Special types for representing /datum/proc paths - ProcStub = 8, - VerbStub = 9 + Appearance = 7 // @formatter:on } @@ -44,9 +40,7 @@ public enum DreamValueTypeFlag { DreamObject = 1 << (DreamValueType.DreamObject - 1), DreamType = 1 << (DreamValueType.DreamType - 1), DreamProc = 1 << (DreamValueType.DreamProc - 1), - Appearance = 1 << (DreamValueType.Appearance - 1), - ProcStub = 1 << (DreamValueType.ProcStub - 1), - VerbStub = 1 << (DreamValueType.VerbStub - 1) + Appearance = 1 << (DreamValueType.Appearance - 1) // @formatter:on } @@ -122,20 +116,6 @@ private DreamValue(DreamValueType type, object refValue) { _refValue = refValue; } - public static DreamValue CreateProcStub(TreeEntry type) { - return new DreamValue( - DreamValueType.ProcStub, - type - ); - } - - public static DreamValue CreateVerbStub(TreeEntry type) { - return new DreamValue( - DreamValueType.VerbStub, - type - ); - } - public override string ToString() { if (Type == DreamValueType.Float) return _floatValue.ToString(); @@ -339,30 +319,6 @@ public DreamProc MustGetValueAsProc() { throw new InvalidCastException("Value " + this + " was not the expected type of DreamProc"); } - public bool TryGetValueAsProcStub([NotNullWhen(true)] out TreeEntry? type) { - if (Type == DreamValueType.ProcStub) { - type = Unsafe.As(_refValue)!; - - return true; - } else { - type = null; - - return false; - } - } - - public bool TryGetValueAsVerbStub([NotNullWhen(true)] out TreeEntry? type) { - if (Type == DreamValueType.VerbStub) { - type = Unsafe.As(_refValue)!; - - return true; - } else { - type = null; - - return false; - } - } - public bool TryGetValueAsAppearance([NotNullWhen(true)] out IconAppearance? args) { if (Type == DreamValueType.Appearance) { args = Unsafe.As(_refValue)!; @@ -395,8 +351,6 @@ public bool IsTruthy() { case DreamValueType.DreamResource: case DreamValueType.DreamType: case DreamValueType.DreamProc: - case DreamValueType.ProcStub: - case DreamValueType.VerbStub: case DreamValueType.Appearance: return true; default: @@ -431,12 +385,6 @@ public string Stringify() { var proc = MustGetValueAsProc(); return proc.ToString(); - case DreamValueType.ProcStub: - case DreamValueType.VerbStub: - var owner = Unsafe.As(_refValue)!; - var lastElement = (Type == DreamValueType.ProcStub) ? "/proc" : "/verb"; - - return $"{owner.Path}{lastElement}"; case DreamValueType.DreamObject: { TryGetValueAsDreamObject(out var dreamObject); diff --git a/OpenDreamRuntime/Objects/DreamObjectTree.cs b/OpenDreamRuntime/Objects/DreamObjectTree.cs index bef8748be0..27e0b17553 100644 --- a/OpenDreamRuntime/Objects/DreamObjectTree.cs +++ b/OpenDreamRuntime/Objects/DreamObjectTree.cs @@ -209,16 +209,6 @@ public DreamValue GetDreamValueFromJsonElement(object? value) { return new DreamValue(Types[typeValue.GetInt32()]); case JsonVariableType.Proc: return new DreamValue(Procs[jsonElement.GetProperty("value").GetInt32()]); - case JsonVariableType.ProcStub: { - TreeEntry type = Types[jsonElement.GetProperty("value").GetInt32()]; - - return DreamValue.CreateProcStub(type); - } - case JsonVariableType.VerbStub: { - TreeEntry type = Types[jsonElement.GetProperty("value").GetInt32()]; - - return DreamValue.CreateVerbStub(type); - } case JsonVariableType.List: DreamList list = CreateList(); diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index da20d51b32..cef9969808 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -655,22 +655,6 @@ public static ProcStatus PushProc(DMProcState state) { return ProcStatus.Continue; } - public static ProcStatus PushProcStub(DMProcState state) { - int ownerTypeId = state.ReadInt(); - var owner = state.Proc.ObjectTree.GetTreeEntry(ownerTypeId); - - state.Push(DreamValue.CreateProcStub(owner)); - return ProcStatus.Continue; - } - - public static ProcStatus PushVerbStub(DMProcState state) { - int ownerTypeId = state.ReadInt(); - var owner = state.Proc.ObjectTree.GetTreeEntry(ownerTypeId); - - state.Push(DreamValue.CreateVerbStub(owner)); - return ProcStatus.Continue; - } - public static ProcStatus PushResource(DMProcState state) { string resourcePath = state.ReadString(); @@ -695,10 +679,16 @@ public static ProcStatus Add(DMProcState state) { DreamValue first = state.Pop(); DreamValue output = default; - if (second.IsNull) { - output = first; - } else if (first.IsNull) { + if (first.IsNull) { output = second; + } else if (first.TryGetValueAsDreamResource(out _) || first.TryGetValueAsDreamObject(out _)) { + output = IconOperationAdd(state, first, second); + } else if (first.TryGetValueAsDreamObject(out var firstDreamObject)) { + output = firstDreamObject!.OperatorAdd(second); + } else if (first.TryGetValueAsType(out _) || first.TryGetValueAsProc(out _)) { + output = default; // Always errors + } else if (second.IsNull) { + output = first; } else switch (first.Type) { case DreamValue.DreamValueType.Float: { float firstFloat = first.MustGetValueAsFloat(); @@ -711,10 +701,6 @@ public static ProcStatus Add(DMProcState state) { case DreamValue.DreamValueType.String when second.Type == DreamValue.DreamValueType.String: output = new DreamValue(first.MustGetValueAsString() + second.MustGetValueAsString()); break; - case DreamValue.DreamValueType.DreamObject: { - output = first.MustGetValueAsDreamObject()!.OperatorAdd(second); - break; - } } if (output.Type != 0) { @@ -738,15 +724,7 @@ public static ProcStatus Append(DMProcState state) { DreamValue result; if (first.TryGetValueAsDreamResource(out _) || first.TryGetValueAsDreamObject(out _)) { - // Implicitly create a new /icon and ICON_ADD blend it - // Note that BYOND creates something other than an /icon, but it behaves the same as one in most reasonable interactions - var iconObj = state.Proc.ObjectTree.CreateObject(state.Proc.ObjectTree.Icon); - if (!state.Proc.DreamResourceManager.TryLoadIcon(first, out var from)) - throw new Exception($"Failed to create an icon from {from}"); - - iconObj.Icon.InsertStates(from, DreamValue.Null, DreamValue.Null, DreamValue.Null); - DreamProcNativeIcon.Blend(iconObj.Icon, second, DreamIconOperationBlend.BlendType.Add, 0, 0); - result = new DreamValue(iconObj); + result = IconOperationAdd(state, first, second); } else if (first.TryGetValueAsDreamObject(out var firstObj)) { if (firstObj != null) { state.PopReference(reference); @@ -2427,8 +2405,6 @@ private static bool IsEqual(DreamValue first, DreamValue second) { case DreamValue.DreamValueType.DreamObject: return firstValue == second.MustGetValueAsDreamObject(); case DreamValue.DreamValueType.Appearance: case DreamValue.DreamValueType.DreamProc: - case DreamValue.DreamValueType.ProcStub: - case DreamValue.DreamValueType.VerbStub: case DreamValue.DreamValueType.DreamType: case DreamValue.DreamValueType.String: case DreamValue.DreamValueType.Float: return false; @@ -2753,6 +2729,18 @@ private static DreamValue CalculateGradient(List gradientValues, Dre return new DreamValue(returnVal.ToHexNoAlpha().ToLower()); return new DreamValue(returnVal.ToHex().ToLower()); } + + private static DreamValue IconOperationAdd(DMProcState state, DreamValue icon, DreamValue blend) { + // Create a new /icon and ICON_ADD blend it + // Note that BYOND creates something other than an /icon, but it behaves the same as one in most reasonable interactions + var iconObj = state.Proc.ObjectTree.CreateObject(state.Proc.ObjectTree.Icon); + if (!state.Proc.DreamResourceManager.TryLoadIcon(icon, out var from)) + throw new Exception($"Failed to create an icon from {from}"); + + iconObj.Icon.InsertStates(from, DreamValue.Null, DreamValue.Null, DreamValue.Null); + DreamProcNativeIcon.Blend(iconObj.Icon, blend, DreamIconOperationBlend.BlendType.Add, 0, 0); + return new DreamValue(iconObj); + } #endregion Helpers } } diff --git a/OpenDreamRuntime/Procs/DMProc.cs b/OpenDreamRuntime/Procs/DMProc.cs index ca8d51a094..b8c155ed82 100644 --- a/OpenDreamRuntime/Procs/DMProc.cs +++ b/OpenDreamRuntime/Procs/DMProc.cs @@ -268,8 +268,6 @@ public sealed class DMProcState : ProcState { {DreamProcOpcode.PushGlobalVars, DMOpcodeHandlers.PushGlobalVars}, {DreamProcOpcode.ModulusModulus, DMOpcodeHandlers.ModulusModulus}, {DreamProcOpcode.ModulusModulusReference, DMOpcodeHandlers.ModulusModulusReference}, - {DreamProcOpcode.PushProcStub, DMOpcodeHandlers.PushProcStub}, - {DreamProcOpcode.PushVerbStub, DMOpcodeHandlers.PushVerbStub}, {DreamProcOpcode.AssignInto, DMOpcodeHandlers.AssignInto}, {DreamProcOpcode.JumpIfNull, DMOpcodeHandlers.JumpIfNull}, {DreamProcOpcode.JumpIfNullNoPop, DMOpcodeHandlers.JumpIfNullNoPop}, diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs index d3c7d7133a..f03ca3ed29 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs @@ -2707,7 +2707,7 @@ public static DreamValue NativeProc_turn(NativeProc.Bundle bundle, DreamObject? } [DreamProc("typesof")] - [DreamProcParameter("Item1", Type = DreamValueTypeFlag.DreamType | DreamValueTypeFlag.DreamObject | DreamValueTypeFlag.ProcStub | DreamValueTypeFlag.VerbStub)] + [DreamProcParameter("Item1", Type = DreamValueTypeFlag.DreamType | DreamValueTypeFlag.DreamObject | DreamValueTypeFlag.String)] public static DreamValue NativeProc_typesof(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { DreamList list = bundle.ObjectTree.CreateList(bundle.Arguments.Length); // Assume every arg will add at least one type @@ -2723,18 +2723,15 @@ public static DreamValue NativeProc_typesof(NativeProc.Bundle bundle, DreamObjec } else if (typeValue.TryGetValueAsString(out var typeString)) { DreamPath path = new DreamPath(typeString); - if (path.LastElement is "proc" or "verb") { + if (path.LastElement == "proc") { type = bundle.ObjectTree.GetTreeEntry(path.FromElements(0, -2)); addingProcs = type.ObjectDefinition.Procs.Values; + } else if (path.LastElement == "verb") { + type = bundle.ObjectTree.GetTreeEntry(path.FromElements(0, -2)); + addingProcs = type.ObjectDefinition.Verbs; } else { type = bundle.ObjectTree.GetTreeEntry(path); } - } else if (typeValue.TryGetValueAsProcStub(out var owner)) { - type = owner; - addingProcs = type.ObjectDefinition.Procs.Values; - } else if (typeValue.TryGetValueAsVerbStub(out owner)) { - type = owner; - addingProcs = type.ObjectDefinition.Verbs; } else { continue; } diff --git a/OpenDreamRuntime/Procs/ProcDecoder.cs b/OpenDreamRuntime/Procs/ProcDecoder.cs index 7c35a73acb..3386663015 100644 --- a/OpenDreamRuntime/Procs/ProcDecoder.cs +++ b/OpenDreamRuntime/Procs/ProcDecoder.cs @@ -133,8 +133,6 @@ public ITuple DecodeInstruction() { case DreamProcOpcode.JumpIfTrue: case DreamProcOpcode.PushType: case DreamProcOpcode.PushProc: - case DreamProcOpcode.PushProcStub: - case DreamProcOpcode.PushVerbStub: case DreamProcOpcode.MassConcatenation: case DreamProcOpcode.JumpIfNull: case DreamProcOpcode.JumpIfNullNoPop: diff --git a/OpenDreamShared/Json/DreamObjectJson.cs b/OpenDreamShared/Json/DreamObjectJson.cs index bc5421823f..f48aa7b87a 100644 --- a/OpenDreamShared/Json/DreamObjectJson.cs +++ b/OpenDreamShared/Json/DreamObjectJson.cs @@ -6,10 +6,8 @@ public enum JsonVariableType { Type = 1, Proc = 2, List = 3, - ProcStub = 4, - VerbStub = 5, - PositiveInfinity = 6, - NegativeInfinity = 7 + PositiveInfinity = 4, + NegativeInfinity = 5 } public sealed class DreamTypeJson { From 09fbc7aae022b9811723ef81376066fe5af70ec0 Mon Sep 17 00:00:00 2001 From: wixoa Date: Sat, 14 Oct 2023 00:52:35 -0400 Subject: [PATCH 16/31] Move DMCompiler's build output path (#1489) --- .github/workflows/compiler-test.yml | 6 +++--- DMCompiler/DMCompiler.csproj | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/compiler-test.yml b/.github/workflows/compiler-test.yml index 39d7e38ea4..329007ebf9 100644 --- a/.github/workflows/compiler-test.yml +++ b/.github/workflows/compiler-test.yml @@ -33,14 +33,14 @@ jobs: - name: Build run: dotnet build main/OpenDream.sln --configuration Release --no-restore /m - name: Compile TestGame - run: main\DMCompiler\bin\Release\net7.0\DMCompiler.exe main\TestGame\environment.dme + run: main\bin\DMCompiler\DMCompiler.exe main\TestGame\environment.dme - name: Checkout Modified /tg/station uses: actions/checkout@v2 with: repository: wixoaGit/tgstation path: tg - name: Compile Modified /tg/station - run: main\DMCompiler\bin\Release\net7.0\DMCompiler.exe tg\tgstation.dme + run: main\bin\DMCompiler\DMCompiler.exe tg\tgstation.dme - name: Checkout 64-bit Paradise uses: actions/checkout@v2 with: @@ -48,4 +48,4 @@ jobs: ref: rustg_64 path: para - name: Compile 64-bit Paradise - run: main\DMCompiler\bin\Release\net7.0\DMCompiler.exe para\paradise.dme + run: main\bin\DMCompiler\DMCompiler.exe para\paradise.dme diff --git a/DMCompiler/DMCompiler.csproj b/DMCompiler/DMCompiler.csproj index 9b7014b99e..c533fdee4f 100644 --- a/DMCompiler/DMCompiler.csproj +++ b/DMCompiler/DMCompiler.csproj @@ -6,6 +6,8 @@ enable Debug;Release;Tools AnyCPU + false + ..\bin\DMCompiler\ From d75c0b5435464215a3ae4c16d4e3b8673d5afdb3 Mon Sep 17 00:00:00 2001 From: wixoa Date: Sat, 14 Oct 2023 12:07:13 -0400 Subject: [PATCH 17/31] Add a performance warning when OpenDream is compiled in Debug (#1487) * Add a performance warning when OD was compiled in Debug * Add a similar warning to the compiler * Some code formatting fixes Bring `OpenDreamClient.States` in line with the rest of the codebase --- DMCompiler/DMCompiler.cs | 6 +- DMCompiler/Program.cs | 11 +- .../Connecting/ConnectingControl.xaml.cs | 44 ++-- .../States/Connecting/ConnectingState.cs | 30 +-- .../States/DreamUserInterfaceStateManager.cs | 74 +++--- OpenDreamClient/States/InGameState.cs | 16 +- .../States/MainMenu/MainMenuControl.xaml | 60 +++-- .../States/MainMenu/MainMenuControl.xaml.cs | 49 ++-- .../States/MainMenu/MainMenuState.cs | 230 ++++++++---------- OpenDreamServer/Program.cs | 15 +- 10 files changed, 251 insertions(+), 284 deletions(-) diff --git a/DMCompiler/DMCompiler.cs b/DMCompiler/DMCompiler.cs index 8f7ba89f4d..86f91cd376 100644 --- a/DMCompiler/DMCompiler.cs +++ b/DMCompiler/DMCompiler.cs @@ -42,6 +42,10 @@ public static bool Compile(DMCompilerSettings settings) { _compileStartTime = DateTime.Now; +#if DEBUG + ForcedWarning("This compiler was compiled in the Debug .NET configuration. This will impact compile speed."); +#endif + if (settings.SuppressUnimplementedWarnings) { ForcedWarning("Unimplemented proc & var warnings are currently suppressed"); } @@ -83,7 +87,7 @@ public static bool Compile(DMCompilerSettings settings) { public static void AddResourceDirectory(string dir) { dir = dir.Replace('\\', Path.DirectorySeparatorChar); - + _resourceDirectories.Add(dir); } diff --git a/DMCompiler/Program.cs b/DMCompiler/Program.cs index 51b7f0d3cc..8cbd5c0a3e 100644 --- a/DMCompiler/Program.cs +++ b/DMCompiler/Program.cs @@ -1,24 +1,19 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.IO; using System.Linq; -using OpenDreamShared.Compiler; using Robust.Shared.Utility; namespace DMCompiler { - - struct Argument { + internal struct Argument { /// The text we found that's in the '--whatever' format. May be null if no such text was present. public string? Name; /// The value, either set in a '--whatever=whoever' format or just left by itself anonymously. May be null. public string? Value; } - - - class Program { - static void Main(string[] args) { + internal static class Program { + private static void Main(string[] args) { if (!TryParseArguments(args, out DMCompilerSettings settings)) { Environment.Exit(1); return; diff --git a/OpenDreamClient/States/Connecting/ConnectingControl.xaml.cs b/OpenDreamClient/States/Connecting/ConnectingControl.xaml.cs index 6fff63b762..f11ec9be97 100644 --- a/OpenDreamClient/States/Connecting/ConnectingControl.xaml.cs +++ b/OpenDreamClient/States/Connecting/ConnectingControl.xaml.cs @@ -6,37 +6,33 @@ using Robust.Client.UserInterface.XAML; using Robust.Shared.Configuration; -namespace OpenDreamClient.States.Connecting -{ - [GenerateTypedNameReferences] - public sealed partial class ConnectingControl : Control - { - public ConnectingControl(IResourceCache resCache, IConfigurationManager configMan) - { +namespace OpenDreamClient.States.Connecting; - RobustXamlLoader.Load(this); +[GenerateTypedNameReferences] +public sealed partial class ConnectingControl : Control { + public ConnectingControl(IResourceCache resCache, IConfigurationManager configMan) { + RobustXamlLoader.Load(this); - Panel.PanelOverride = new StyleBoxFlat(Color.Black); + Panel.PanelOverride = new StyleBoxFlat(Color.Black); - ConnectingLabel.FontOverride = new VectorFont(resCache.GetResource("/Fonts/NotoSans-Regular.ttf"), 24); - WIPLabel.FontOverride = new VectorFont(resCache.GetResource("/Fonts/NotoSans-Bold.ttf"), 32); + ConnectingLabel.FontOverride = new VectorFont(resCache.GetResource("/Fonts/NotoSans-Regular.ttf"), 24); + WIPLabel.FontOverride = new VectorFont(resCache.GetResource("/Fonts/NotoSans-Bold.ttf"), 32); - LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide); + LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide); - LayoutContainer.SetAnchorPreset(VBox, LayoutContainer.LayoutPreset.Center); - LayoutContainer.SetGrowHorizontal(VBox, LayoutContainer.GrowDirection.Both); - LayoutContainer.SetGrowVertical(VBox, LayoutContainer.GrowDirection.Both); + LayoutContainer.SetAnchorPreset(VBox, LayoutContainer.LayoutPreset.Center); + LayoutContainer.SetGrowHorizontal(VBox, LayoutContainer.GrowDirection.Both); + LayoutContainer.SetGrowVertical(VBox, LayoutContainer.GrowDirection.Both); - LayoutContainer.SetAnchorPreset(ConnectingLabel, LayoutContainer.LayoutPreset.Center); - LayoutContainer.SetGrowHorizontal(ConnectingLabel, LayoutContainer.GrowDirection.Both); - LayoutContainer.SetGrowVertical(ConnectingLabel, LayoutContainer.GrowDirection.Both); + LayoutContainer.SetAnchorPreset(ConnectingLabel, LayoutContainer.LayoutPreset.Center); + LayoutContainer.SetGrowHorizontal(ConnectingLabel, LayoutContainer.GrowDirection.Both); + LayoutContainer.SetGrowVertical(ConnectingLabel, LayoutContainer.GrowDirection.Both); - LayoutContainer.SetAnchorPreset(WIP, LayoutContainer.LayoutPreset.VerticalCenterWide); - LayoutContainer.SetGrowHorizontal(WIP, LayoutContainer.GrowDirection.Both); - LayoutContainer.SetGrowVertical(WIP, LayoutContainer.GrowDirection.Both); + LayoutContainer.SetAnchorPreset(WIP, LayoutContainer.LayoutPreset.VerticalCenterWide); + LayoutContainer.SetGrowHorizontal(WIP, LayoutContainer.GrowDirection.Both); + LayoutContainer.SetGrowVertical(WIP, LayoutContainer.GrowDirection.Both); - var logoTexture = resCache.GetResource("/OpenDream/Logo/logo.png"); - Logo.Texture = logoTexture; - } + var logoTexture = resCache.GetResource("/OpenDream/Logo/logo.png"); + Logo.Texture = logoTexture; } } diff --git a/OpenDreamClient/States/Connecting/ConnectingState.cs b/OpenDreamClient/States/Connecting/ConnectingState.cs index b8e35c93e1..2a521e4b5b 100644 --- a/OpenDreamClient/States/Connecting/ConnectingState.cs +++ b/OpenDreamClient/States/Connecting/ConnectingState.cs @@ -3,25 +3,21 @@ using Robust.Client.UserInterface; using Robust.Shared.Configuration; -namespace OpenDreamClient.States.Connecting -{ - public sealed class ConnectingState : State - { - [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; - [Dependency] private readonly IResourceCache _resourceCache = default!; - [Dependency] private readonly IConfigurationManager _configurationManager = default!; +namespace OpenDreamClient.States.Connecting; - private ConnectingControl _connectingControl = default!; +public sealed class ConnectingState : State { + [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; - protected override void Startup() - { - _connectingControl = new ConnectingControl(_resourceCache, _configurationManager); - _userInterfaceManager.StateRoot.AddChild(_connectingControl); - } + private ConnectingControl _connectingControl = default!; - protected override void Shutdown() - { - _connectingControl.Dispose(); - } + protected override void Startup() { + _connectingControl = new ConnectingControl(_resourceCache, _configurationManager); + _userInterfaceManager.StateRoot.AddChild(_connectingControl); + } + + protected override void Shutdown() { + _connectingControl.Dispose(); } } diff --git a/OpenDreamClient/States/DreamUserInterfaceStateManager.cs b/OpenDreamClient/States/DreamUserInterfaceStateManager.cs index d8a86a6dc3..609e7b29c6 100644 --- a/OpenDreamClient/States/DreamUserInterfaceStateManager.cs +++ b/OpenDreamClient/States/DreamUserInterfaceStateManager.cs @@ -4,51 +4,45 @@ using Robust.Client; using Robust.Client.State; -namespace OpenDreamClient.States -{ - /// - /// Handles changing the UI state depending on connection status. - /// - [UsedImplicitly] - public sealed class DreamUserInterfaceStateManager - { - [Dependency] private readonly IGameController _gameController = default!; - [Dependency] private readonly IBaseClient _client = default!; - [Dependency] private readonly IStateManager _stateManager = default!; +namespace OpenDreamClient.States; - public void Initialize() - { - _client.RunLevelChanged += ((_, args) => - { - switch (args.NewLevel) - { - case ClientRunLevel.InGame: - case ClientRunLevel.Connected: - case ClientRunLevel.SinglePlayerGame: - _stateManager.RequestStateChange(); - break; - - case ClientRunLevel.Initialize when args.OldLevel < ClientRunLevel.Connected: - _stateManager.RequestStateChange(); - break; +/// +/// Handles changing the UI state depending on connection status. +/// +[UsedImplicitly] +public sealed class DreamUserInterfaceStateManager { + [Dependency] private readonly IGameController _gameController = default!; + [Dependency] private readonly IBaseClient _client = default!; + [Dependency] private readonly IStateManager _stateManager = default!; - // When we disconnect from the server: - case ClientRunLevel.Error: - case ClientRunLevel.Initialize when args.OldLevel >= ClientRunLevel.Connected: - if (_gameController.LaunchState.FromLauncher) - { - _stateManager.RequestStateChange(); - break; - } + public void Initialize() { + _client.RunLevelChanged += ((_, args) => { + switch (args.NewLevel) { + case ClientRunLevel.InGame: + case ClientRunLevel.Connected: + case ClientRunLevel.SinglePlayerGame: + _stateManager.RequestStateChange(); + break; - _stateManager.RequestStateChange(); - break; + case ClientRunLevel.Initialize when args.OldLevel < ClientRunLevel.Connected: + _stateManager.RequestStateChange(); + break; - case ClientRunLevel.Connecting: + // When we disconnect from the server: + case ClientRunLevel.Error: + case ClientRunLevel.Initialize when args.OldLevel >= ClientRunLevel.Connected: + if (_gameController.LaunchState.FromLauncher) { _stateManager.RequestStateChange(); break; - } - }); - } + } + + _stateManager.RequestStateChange(); + break; + + case ClientRunLevel.Connecting: + _stateManager.RequestStateChange(); + break; + } + }); } } diff --git a/OpenDreamClient/States/InGameState.cs b/OpenDreamClient/States/InGameState.cs index 5218d7615a..cb5b5430fd 100644 --- a/OpenDreamClient/States/InGameState.cs +++ b/OpenDreamClient/States/InGameState.cs @@ -1,15 +1,11 @@ using Robust.Client.State; -namespace OpenDreamClient.States -{ - public sealed class InGameState : State - { - protected override void Startup() - { - } +namespace OpenDreamClient.States; - protected override void Shutdown() - { - } +public sealed class InGameState : State { + protected override void Startup() { + } + + protected override void Shutdown() { } } diff --git a/OpenDreamClient/States/MainMenu/MainMenuControl.xaml b/OpenDreamClient/States/MainMenu/MainMenuControl.xaml index ce59f5801a..2dc42010df 100644 --- a/OpenDreamClient/States/MainMenu/MainMenuControl.xaml +++ b/OpenDreamClient/States/MainMenu/MainMenuControl.xaml @@ -6,33 +6,45 @@ Orientation="Vertical"> - - - - -