From e902aaede20438145c209c0091cdafe82e4de19c Mon Sep 17 00:00:00 2001 From: wixoaGit Date: Wed, 14 Dec 2022 11:51:27 -0500 Subject: [PATCH 1/3] Refactor `DreamObjectDefinition.IsSubtypeOf()` It now takes a TreeEntry and runs in O(1) time --- Content.Tests/DMTests.cs | 11 +- DMCompiler/DMCompiler.cs | 3 +- OpenDream.sln.DotSettings | 1 + OpenDreamRuntime/AtomManager.cs | 9 +- OpenDreamRuntime/DreamConnection.cs | 23 +- OpenDreamRuntime/DreamManager.Connections.cs | 2 +- OpenDreamRuntime/DreamManager.cs | 56 ++--- OpenDreamRuntime/DreamMapManager.cs | 45 ++-- OpenDreamRuntime/DreamValue.cs | 30 +-- OpenDreamRuntime/IDreamManager.cs | 1 - OpenDreamRuntime/Objects/DreamIcon.cs | 3 +- OpenDreamRuntime/Objects/DreamList.cs | 28 +-- OpenDreamRuntime/Objects/DreamObject.cs | 5 +- .../Objects/DreamObjectDefinition.cs | 77 +++---- OpenDreamRuntime/Objects/DreamObjectTree.cs | 210 ++++++++++++++---- .../MetaObjects/DreamMetaObjectArea.cs | 3 +- .../MetaObjects/DreamMetaObjectAtom.cs | 19 +- .../MetaObjects/DreamMetaObjectClient.cs | 13 +- .../MetaObjects/DreamMetaObjectDatum.cs | 17 +- .../MetaObjects/DreamMetaObjectIcon.cs | 7 +- .../MetaObjects/DreamMetaObjectMatrix.cs | 27 ++- .../Objects/MetaObjects/DreamMetaObjectMob.cs | 7 +- .../MetaObjects/DreamMetaObjectMovable.cs | 7 +- .../MetaObjects/DreamMetaObjectRegex.cs | 8 +- OpenDreamRuntime/Procs/DMOpcodeHandlers.cs | 113 +++++----- OpenDreamRuntime/Procs/DMProc.cs | 18 +- OpenDreamRuntime/Procs/DreamEnumerators.cs | 139 +++++++----- OpenDreamRuntime/Procs/InitDreamObject.cs | 4 +- .../Procs/Native/DreamProcNative.cs | 37 +-- .../Procs/Native/DreamProcNativeIcon.cs | 3 +- .../Procs/Native/DreamProcNativeRegex.cs | 4 +- .../Procs/Native/DreamProcNativeRoot.cs | 55 ++--- OpenDreamRuntime/ServerContentIoC.cs | 4 +- 33 files changed, 563 insertions(+), 426 deletions(-) diff --git a/Content.Tests/DMTests.cs b/Content.Tests/DMTests.cs index da930b859d..ee8dc48352 100644 --- a/Content.Tests/DMTests.cs +++ b/Content.Tests/DMTests.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using NUnit.Framework; using OpenDreamRuntime; +using OpenDreamRuntime.Objects; using OpenDreamRuntime.Procs; using OpenDreamRuntime.Rendering; using Robust.Shared.Asynchronous; @@ -14,12 +15,12 @@ namespace Content.Tests { [TestFixture] - public sealed partial class DMTests : ContentUnitTest - { + public sealed class DMTests : ContentUnitTest { public const string TestProject = "DMProject"; public const string InitializeEnvironment = "./environment.dme"; private IDreamManager _dreamMan; + private IDreamObjectTree _objectTree; private ITaskManager _taskManager; [Flags] @@ -41,6 +42,7 @@ public void OneTimeSetup() componentFactory.RegisterClass(); componentFactory.GenerateNetIds(); _dreamMan = IoCManager.Resolve(); + _objectTree = IoCManager.Resolve(); Compile(InitializeEnvironment); _dreamMan.PreInitialize(Path.ChangeExtension(InitializeEnvironment, "json")); } @@ -61,8 +63,7 @@ public void Cleanup(string compiledFile) { } [Test, TestCaseSource(nameof(GetTests))] - public void TestFiles(string sourceFile, DMTestFlags testFlags) - { + public void TestFiles(string sourceFile, DMTestFlags testFlags) { string initialDirectory = Directory.GetCurrentDirectory(); try { string compiledFile = Compile(Path.Join(initialDirectory, "Tests", sourceFile)); @@ -112,7 +113,7 @@ public void TestFiles(string sourceFile, DMTestFlags testFlags) Task callTask = null; DreamThread.Run("RunTest", async (state) => { - if (_dreamMan.ObjectTree.TryGetGlobalProc("RunTest", out DreamProc proc)) { + if (_objectTree.TryGetGlobalProc("RunTest", out DreamProc proc)) { callTask = state.Call(proc, null, null, new DreamProcArguments(null)); result = await callTask; return DreamValue.Null; diff --git a/DMCompiler/DMCompiler.cs b/DMCompiler/DMCompiler.cs index 7dbd28c2ec..b4ffd07e94 100644 --- a/DMCompiler/DMCompiler.cs +++ b/DMCompiler/DMCompiler.cs @@ -270,8 +270,7 @@ private static List ConvertMaps(List mapPaths) { return maps; } - private static string SaveJson(List maps, string interfaceFile, string outputFile) - { + private static string SaveJson(List maps, string interfaceFile, string outputFile) { DreamCompiledJson compiledDream = new DreamCompiledJson(); compiledDream.Strings = DMObjectTree.StringTable; compiledDream.Maps = maps; diff --git a/OpenDream.sln.DotSettings b/OpenDream.sln.DotSettings index 7f384db4e9..53ff1a6d10 100644 --- a/OpenDream.sln.DotSettings +++ b/OpenDream.sln.DotSettings @@ -7,6 +7,7 @@ True True True + True True True True \ No newline at end of file diff --git a/OpenDreamRuntime/AtomManager.cs b/OpenDreamRuntime/AtomManager.cs index 90a1476f2c..699dc4fbc8 100644 --- a/OpenDreamRuntime/AtomManager.cs +++ b/OpenDreamRuntime/AtomManager.cs @@ -12,6 +12,7 @@ internal sealed class AtomManager : IAtomManager { public Dictionary UnderlaysListToAtom { get; } = new(); [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IDreamObjectTree _objectTree = default!; [Dependency] private readonly IDreamMapManager _dreamMapManager = default!; private readonly Dictionary _atomToEntity = new(); @@ -52,17 +53,17 @@ public void DeleteMovableEntity(DreamObject movable) { } public IconAppearance? GetAppearance(DreamObject atom) { - return atom.IsSubtypeOf(DreamPath.Turf) + return atom.IsSubtypeOf(_objectTree.Turf) ? _dreamMapManager.GetTurfAppearance(atom) : _entityManager.GetComponent(GetMovableEntity(atom)).Appearance; } public void UpdateAppearance(DreamObject atom, Action update) { - if (atom.IsSubtypeOf(DreamPath.Turf)) { + if (atom.IsSubtypeOf(_objectTree.Turf)) { IconAppearance appearance = new IconAppearance(_dreamMapManager.GetTurfAppearance(atom)); update(appearance); _dreamMapManager.SetTurfAppearance(atom, appearance); - } else if (atom.IsSubtypeOf(DreamPath.Movable)) { + } else if (atom.IsSubtypeOf(_objectTree.Movable)) { if (!_entityManager.TryGetComponent(GetMovableEntity(atom), out var sprite)) return; @@ -73,7 +74,7 @@ public void UpdateAppearance(DreamObject atom, Action update) { } public void AnimateAppearance(DreamObject atom, TimeSpan duration, Action animate) { - if (!atom.IsSubtypeOf(DreamPath.Movable)) + if (!atom.IsSubtypeOf(_objectTree.Movable)) return; //Animating non-movables is unimplemented if (!_entityManager.TryGetComponent(GetMovableEntity(atom), out var sprite)) return; diff --git a/OpenDreamRuntime/DreamConnection.cs b/OpenDreamRuntime/DreamConnection.cs index 1f7091fd40..a28d65b0fb 100644 --- a/OpenDreamRuntime/DreamConnection.cs +++ b/OpenDreamRuntime/DreamConnection.cs @@ -15,6 +15,7 @@ namespace OpenDreamRuntime public sealed class DreamConnection { [Dependency] private readonly IDreamManager _dreamManager = default!; + [Dependency] private readonly IDreamObjectTree _objectTree = default!; [Dependency] private readonly IAtomManager _atomManager = default!; [Dependency] private readonly DreamResourceManager _resourceManager = default!; @@ -47,22 +48,18 @@ public DreamObject MobDreamObject get => _mobDreamObject; set { - if (_mobDreamObject != value) - { + if (_mobDreamObject != value) { if (_mobDreamObject != null) _mobDreamObject.SpawnProc("Logout"); - if (value != null && value.IsSubtypeOf(DreamPath.Mob)) - { + if (value != null && value.IsSubtypeOf(_objectTree.Mob)) { DreamConnection oldMobConnection = _dreamManager.GetConnectionFromMob(value); if (oldMobConnection != null) oldMobConnection.MobDreamObject = null; _mobDreamObject = value; ClientDreamObject?.SetVariable("eye", new DreamValue(_mobDreamObject)); - _mobDreamObject.SpawnProc("Login", usr: _mobDreamObject ); + _mobDreamObject.SpawnProc("Login", usr: _mobDreamObject); Session.AttachToEntity(_atomManager.GetMovableEntity(_mobDreamObject)); - } - else - { + } else { Session.DetachFromEntity(); _mobDreamObject = null; } @@ -212,7 +209,7 @@ public void HandleMsgTopic(MsgTopic pTopic) { public void OutputDreamValue(DreamValue value) { if (value.TryGetValueAsDreamObject(out DreamObject outputObject)) { - if (outputObject?.IsSubtypeOf(DreamPath.Sound) == true) { + if (outputObject?.IsSubtypeOf(_objectTree.Sound) == true) { UInt16 channel = (UInt16)outputObject.GetVariable("channel").GetValueAsInteger(); UInt16 volume = (UInt16)outputObject.GetVariable("volume").GetValueAsInteger(); DreamValue file = outputObject.GetVariable("file"); @@ -317,13 +314,13 @@ public async Task PromptList(DMValueType types, DreamList list, Stri for (int i = 0; i < listValues.Count; i++) { DreamValue value = listValues[i]; - if (types.HasFlag(DMValueType.Obj) && !value.TryGetValueAsDreamObjectOfType(DreamPath.Movable, out _)) + if (types.HasFlag(DMValueType.Obj) && !value.TryGetValueAsDreamObjectOfType(_objectTree.Movable, out _)) continue; - if (types.HasFlag(DMValueType.Mob) && !value.TryGetValueAsDreamObjectOfType(DreamPath.Mob, out _)) + if (types.HasFlag(DMValueType.Mob) && !value.TryGetValueAsDreamObjectOfType(_objectTree.Mob, out _)) continue; - if (types.HasFlag(DMValueType.Turf) && !value.TryGetValueAsDreamObjectOfType(DreamPath.Turf, out _)) + if (types.HasFlag(DMValueType.Turf) && !value.TryGetValueAsDreamObjectOfType(_objectTree.Turf, out _)) continue; - if (types.HasFlag(DMValueType.Area) && !value.TryGetValueAsDreamObjectOfType(DreamPath.Area, out _)) + if (types.HasFlag(DMValueType.Area) && !value.TryGetValueAsDreamObjectOfType(_objectTree.Area, out _)) continue; promptValues.Add(value.Stringify()); diff --git a/OpenDreamRuntime/DreamManager.Connections.cs b/OpenDreamRuntime/DreamManager.Connections.cs index 75898e618c..36fb216268 100644 --- a/OpenDreamRuntime/DreamManager.Connections.cs +++ b/OpenDreamRuntime/DreamManager.Connections.cs @@ -87,7 +87,7 @@ private void OnPlayerStatusChanged(object sender, SessionStatusEventArgs e) case SessionStatus.InGame: { var connection = new DreamConnection(e.Session); - var client = ObjectTree.CreateObject(DreamPath.Client); + var client = _objectTree.CreateObject(DreamPath.Client); connection.ClientDreamObject = client; _clientToConnection.Add(client, connection); diff --git a/OpenDreamRuntime/DreamManager.cs b/OpenDreamRuntime/DreamManager.cs index d6cbfc07b4..3618f5fd2c 100644 --- a/OpenDreamRuntime/DreamManager.cs +++ b/OpenDreamRuntime/DreamManager.cs @@ -22,8 +22,8 @@ partial class DreamManager : IDreamManager { [Dependency] private readonly IDreamMapManager _dreamMapManager = default!; [Dependency] private readonly IProcScheduler _procScheduler = default!; [Dependency] private readonly DreamResourceManager _dreamResourceManager = default!; + [Dependency] private readonly IDreamObjectTree _objectTree = default!; - public DreamObjectTree ObjectTree { get; private set; } = new(); public DreamObject WorldInstance { get; private set; } public Exception? LastDMException { get; set; } @@ -59,7 +59,7 @@ public void StartWorld() { // Call global with waitfor=FALSE if (_compiledJson.GlobalInitProc is ProcDefinitionJson initProcDef) { - var globalInitProc = new DMProc(DreamPath.Root, "(global init)", null, null, null, initProcDef.Bytecode, initProcDef.MaxStackSize, initProcDef.Attributes, initProcDef.VerbName, initProcDef.VerbCategory, initProcDef.VerbDesc, initProcDef.Invisibility); + var globalInitProc = new DMProc(DreamPath.Root, "(global init)", null, null, null, initProcDef.Bytecode, initProcDef.MaxStackSize, initProcDef.Attributes, initProcDef.VerbName, initProcDef.VerbCategory, initProcDef.VerbDesc, initProcDef.Invisibility, _objectTree); globalInitProc.Spawn(WorldInstance, new DreamProcArguments()); } @@ -100,15 +100,15 @@ public bool LoadJson(string? jsonPath) { if(!string.IsNullOrEmpty(_compiledJson.Interface) && !_dreamResourceManager.DoesFileExist(_compiledJson.Interface)) throw new FileNotFoundException("Interface DMF not found at "+Path.Join(Path.GetDirectoryName(jsonPath),_compiledJson.Interface)); //TODO: Empty or invalid _compiledJson.Interface should return default interface - see issue #851 - ObjectTree.LoadJson(json); + _objectTree.LoadJson(json); SetMetaObjects(); - DreamProcNative.SetupNativeProcs(ObjectTree); + DreamProcNative.SetupNativeProcs(_objectTree); _dreamMapManager.Initialize(); WorldContentsList = DreamList.Create(); - WorldInstance = ObjectTree.CreateObject(DreamPath.World); + WorldInstance = _objectTree.CreateObject(DreamPath.World); // Call /world/. This is an IMPLEMENTATION DETAIL and non-DMStandard should NOT be run here. WorldInstance.InitSpawn(new DreamProcArguments()); @@ -119,7 +119,7 @@ public bool LoadJson(string? jsonPath) { for (int i = 0; i < jsonGlobals.GlobalCount; i++) { object globalValue = jsonGlobals.Globals.GetValueOrDefault(i, null); - Globals.Add(ObjectTree.GetDreamValueFromJsonElement(globalValue)); + Globals.Add(_objectTree.GetDreamValueFromJsonElement(globalValue)); } } @@ -134,22 +134,22 @@ public bool LoadJson(string? jsonPath) { private void SetMetaObjects() { // Datum needs to be set first - ObjectTree.SetMetaObject(DreamPath.Datum, new DreamMetaObjectDatum()); + _objectTree.SetMetaObject(DreamPath.Datum, new DreamMetaObjectDatum()); //TODO Investigate what types BYOND can reparent without exploding and only allow reparenting those - ObjectTree.SetMetaObject(DreamPath.List, new DreamMetaObjectList()); - ObjectTree.SetMetaObject(DreamPath.Client, new DreamMetaObjectClient()); - ObjectTree.SetMetaObject(DreamPath.World, new DreamMetaObjectWorld()); - ObjectTree.SetMetaObject(DreamPath.Matrix, new DreamMetaObjectMatrix()); - ObjectTree.SetMetaObject(DreamPath.Regex, new DreamMetaObjectRegex()); - ObjectTree.SetMetaObject(DreamPath.Atom, new DreamMetaObjectAtom()); - ObjectTree.SetMetaObject(DreamPath.Area, new DreamMetaObjectArea()); - ObjectTree.SetMetaObject(DreamPath.Turf, new DreamMetaObjectTurf()); - ObjectTree.SetMetaObject(DreamPath.Movable, new DreamMetaObjectMovable()); - ObjectTree.SetMetaObject(DreamPath.Mob, new DreamMetaObjectMob()); - ObjectTree.SetMetaObject(DreamPath.Icon, new DreamMetaObjectIcon()); - ObjectTree.SetMetaObject(DreamPath.Filter, new DreamMetaObjectFilter()); - ObjectTree.SetMetaObject(DreamPath.Savefile, new DreamMetaObjectSavefile()); + _objectTree.SetMetaObject(DreamPath.List, new DreamMetaObjectList()); + _objectTree.SetMetaObject(DreamPath.Client, new DreamMetaObjectClient()); + _objectTree.SetMetaObject(DreamPath.World, new DreamMetaObjectWorld()); + _objectTree.SetMetaObject(DreamPath.Matrix, new DreamMetaObjectMatrix()); + _objectTree.SetMetaObject(DreamPath.Regex, new DreamMetaObjectRegex()); + _objectTree.SetMetaObject(DreamPath.Atom, new DreamMetaObjectAtom()); + _objectTree.SetMetaObject(DreamPath.Area, new DreamMetaObjectArea()); + _objectTree.SetMetaObject(DreamPath.Turf, new DreamMetaObjectTurf()); + _objectTree.SetMetaObject(DreamPath.Movable, new DreamMetaObjectMovable()); + _objectTree.SetMetaObject(DreamPath.Mob, new DreamMetaObjectMob()); + _objectTree.SetMetaObject(DreamPath.Icon, new DreamMetaObjectIcon()); + _objectTree.SetMetaObject(DreamPath.Filter, new DreamMetaObjectFilter()); + _objectTree.SetMetaObject(DreamPath.Savefile, new DreamMetaObjectSavefile()); } public void WriteWorldLog(string message, LogLevel level = LogLevel.Info, string sawmill = "world.log") { @@ -195,14 +195,14 @@ public string CreateRef(DreamValue value) { } } else if (value.TryGetValueAsString(out var refStr)) { refType = RefType.String; - idx = ObjectTree.Strings.IndexOf(refStr); + idx = _objectTree.Strings.IndexOf(refStr); if (idx == -1) { - ObjectTree.Strings.Add(refStr); - idx = ObjectTree.Strings.Count - 1; + _objectTree.Strings.Add(refStr); + idx = _objectTree.Strings.Count - 1; } } else if (value.TryGetValueAsPath(out var refPath)) { - var treeEntry = ObjectTree.GetTreeEntry(refPath); + var treeEntry = _objectTree.GetTreeEntry(refPath); refType = RefType.DreamPath; idx = treeEntry.Id; @@ -247,12 +247,12 @@ public DreamValue LocateRef(string refString) { return DreamValue.Null; case RefType.String: - return ObjectTree.Strings.Count > refId - ? new DreamValue(ObjectTree.Strings[refId]) + return _objectTree.Strings.Count > refId + ? new DreamValue(_objectTree.Strings[refId]) : DreamValue.Null; case RefType.DreamPath: - return ObjectTree.Types.Length > refId - ? new DreamValue(ObjectTree.Types[refId].Path) + return _objectTree.Types.Length > refId + ? new DreamValue(_objectTree.Types[refId].Path) : DreamValue.Null; default: throw new Exception($"Invalid reference type for ref {refString}"); diff --git a/OpenDreamRuntime/DreamMapManager.cs b/OpenDreamRuntime/DreamMapManager.cs index c108444ae7..8cab9c8d7b 100644 --- a/OpenDreamRuntime/DreamMapManager.cs +++ b/OpenDreamRuntime/DreamMapManager.cs @@ -52,6 +52,7 @@ public Cell(DreamObject area) { [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IDreamManager _dreamManager = default!; + [Dependency] private readonly IDreamObjectTree _objectTree = default!; [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; private ServerAppearanceSystem _appearanceSystem = default!; // set in Initialize @@ -67,40 +68,32 @@ public Cell(DreamObject area) { public void Initialize() { _appearanceSystem = _entitySystemManager.GetEntitySystem(); - DreamObjectDefinition worldDefinition = _dreamManager.ObjectTree.GetObjectDefinition(DreamPath.World); + DreamObjectDefinition worldDefinition = _objectTree.GetObjectDefinition(DreamPath.World); // Default area - if (worldDefinition.Variables["area"].TryGetValueAsPath(out var area)) - { - if(!_dreamManager.ObjectTree.GetObjectDefinition(area).IsSubtypeOf(DreamPath.Area)) throw new Exception("bad area"); + if (worldDefinition.Variables["area"].TryGetValueAsPath(out var area)) { + if(!_objectTree.GetObjectDefinition(area).IsSubtypeOf(_objectTree.Area)) throw new Exception("bad area"); - _defaultArea = new MapObjectJson(_dreamManager.ObjectTree.GetTreeEntry(area).Id); - } - else if (worldDefinition.Variables["area"] == DreamValue.Null || - worldDefinition.Variables["area"].TryGetValueAsInteger(out var areaInt) && areaInt == 0) - { + _defaultArea = new MapObjectJson(_objectTree.GetTreeEntry(area).Id); + } else if (worldDefinition.Variables["area"] == DreamValue.Null || + worldDefinition.Variables["area"].TryGetValueAsInteger(out var areaInt) && areaInt == 0) { //TODO: Properly handle disabling default area - _defaultArea = new MapObjectJson(_dreamManager.ObjectTree.GetTreeEntry(DreamPath.Area).Id); + _defaultArea = new MapObjectJson(_objectTree.GetTreeEntry(DreamPath.Area).Id); } - else - { + else { throw new Exception("bad area"); } //Default turf - if (worldDefinition.Variables["turf"].TryGetValueAsPath(out var turf)) - { - if(!_dreamManager.ObjectTree.GetObjectDefinition(turf).IsSubtypeOf(DreamPath.Turf)) throw new Exception("bad turf"); + if (worldDefinition.Variables["turf"].TryGetValueAsPath(out var turf)) { + if (!_objectTree.GetObjectDefinition(turf).IsSubtypeOf(_objectTree.Turf)) + throw new Exception("bad turf"); _defaultTurf = turf; - } - else if (worldDefinition.Variables["turf"] == DreamValue.Null || - worldDefinition.Variables["turf"].TryGetValueAsInteger(out var turfInt) && turfInt == 0) - { + } else if (worldDefinition.Variables["turf"] == DreamValue.Null || + worldDefinition.Variables["turf"].TryGetValueAsInteger(out var turfInt) && turfInt == 0) { //TODO: Properly handle disabling default turf _defaultTurf = DreamPath.Turf; - } - else - { + } else { throw new Exception("bad turf"); } } @@ -152,7 +145,7 @@ public void InitializeAtoms(List maps) { // Also call New() on all /area not in the grid. // This may call New() a SECOND TIME. This is intentional. foreach (var thing in _dreamManager.WorldContentsList.GetValues()) { - if (thing.TryGetValueAsDreamObjectOfType(DreamPath.Area, out var area)) { + if (thing.TryGetValueAsDreamObjectOfType(_objectTree.Area, out var area)) { if (seenAreas.Add(area)) { area.SpawnProc("New"); } @@ -259,7 +252,7 @@ public DreamObject GetAreaAt(DreamObject turf) { public void SetZLevels(int levels) { if (levels > Levels) { - DreamObjectDefinition defaultTurfDef = _dreamManager.ObjectTree.GetObjectDefinition(_defaultTurf); + DreamObjectDefinition defaultTurfDef = _objectTree.GetObjectDefinition(_defaultTurf); DreamObject defaultArea = GetOrCreateArea(_defaultArea); for (int z = Levels + 1; z <= levels; z++) { @@ -344,13 +337,13 @@ private void LoadMapObjectsAndMobs(MapBlockJson block, Dictionary 0) { definition = new DreamObjectDefinition(definition); foreach (KeyValuePair varOverride in mapObject.VarOverrides) { if (definition.HasVariable(varOverride.Key)) { - definition.Variables[varOverride.Key] = _dreamManager.ObjectTree.GetDreamValueFromJsonElement(varOverride.Value); + definition.Variables[varOverride.Key] = _objectTree.GetDreamValueFromJsonElement(varOverride.Value); } } } diff --git a/OpenDreamRuntime/DreamValue.cs b/OpenDreamRuntime/DreamValue.cs index 2b26894671..aa7b24ba4b 100644 --- a/OpenDreamRuntime/DreamValue.cs +++ b/OpenDreamRuntime/DreamValue.cs @@ -1,7 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; -using JetBrains.Annotations; using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.MetaObjects; using OpenDreamRuntime.Resources; @@ -10,7 +9,6 @@ using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Markdown; using Robust.Shared.Serialization.Markdown.Validation; -using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Serialization.TypeSerializers.Interfaces; namespace OpenDreamRuntime { @@ -28,8 +26,8 @@ public enum DreamValueType { } public static readonly DreamValue Null = new DreamValue((DreamObject?)null); - public static DreamValue True { get => new DreamValue(1f); } - public static DreamValue False { get => new DreamValue(0f); } + public static DreamValue True => new DreamValue(1f); + public static DreamValue False => new DreamValue(0f); public DreamValueType Type { get; private set; } public object Value { get; private set; } @@ -72,8 +70,8 @@ public DreamValue(DreamProc value) { } public DreamValue(object value) { - if (value is int) { - Value = (float)(int)value; + if (value is int intValue) { + Value = (float)intValue; } else { Value = value; } @@ -223,7 +221,7 @@ public DreamObject MustGetValueAsDreamObject() { } } - public bool TryGetValueAsDreamObjectOfType(DreamPath type, [NotNullWhen(true)] out DreamObject? dreamObject) { + public bool TryGetValueAsDreamObjectOfType(IDreamObjectTree.TreeEntry type, [NotNullWhen(true)] out DreamObject? dreamObject) { return TryGetValueAsDreamObject(out dreamObject) && dreamObject != null && dreamObject.IsSubtypeOf(type); } @@ -232,9 +230,9 @@ public DreamList GetValueAsDreamList() { return MustGetValueAsDreamList(); } - public bool TryGetValueAsDreamList([NotNullWhen(true)] out DreamList list) { - if (TryGetValueAsDreamObjectOfType(DreamPath.List, out DreamObject listObject)) { - list = (DreamList)listObject; + public bool TryGetValueAsDreamList([NotNullWhen(true)] out DreamList? list) { + if (TryGetValueAsDreamObject(out var obj) && obj is DreamList listObject) { + list = listObject; return true; } else { @@ -336,7 +334,7 @@ public string Stringify() { } } - public override bool Equals(object obj) => obj is DreamValue other && Equals(other); + public override bool Equals(object? obj) => obj is DreamValue other && Equals(other); public bool Equals(DreamValue other) { if (Type != other.Type) return false; @@ -362,6 +360,8 @@ public override int GetHashCode() { #region Serialization public sealed class DreamValueJsonConverter : JsonConverter { + private readonly IDreamObjectTree _objectTree = IoCManager.Resolve(); + public override void Write(Utf8JsonWriter writer, DreamValue value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteNumber("Type", (int)value.Type); @@ -371,7 +371,7 @@ public override void Write(Utf8JsonWriter writer, DreamValue value, JsonSerializ case DreamValue.DreamValueType.Float: writer.WriteNumber("Value", (float)value.Value); break; case DreamValue.DreamValueType.DreamObject when value == DreamValue.Null: writer.WriteNull("Value"); break; case DreamValue.DreamValueType.DreamObject - when value.TryGetValueAsDreamObjectOfType(DreamPath.Icon, out var iconObj): + when value.TryGetValueAsDreamObjectOfType(_objectTree.Icon, out var iconObj): { // TODO Check what happens with multiple states var icon = DreamMetaObjectIcon.ObjectToDreamIcon[iconObj]; @@ -517,11 +517,13 @@ public ValidationNode Validate(ISerializationManager serializationManager, Dream [TypeSerializer] public sealed class DreamValueMatrix3Serializer : ITypeReader { + private readonly IDreamObjectTree _objectTree = IoCManager.Resolve(); + public Matrix3 Read(ISerializationManager serializationManager, DreamValueDataNode node, IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null, Matrix3 value = default) { - if (!node.Value.TryGetValueAsDreamObjectOfType(DreamPath.Matrix, out var matrixObject)) + if (!node.Value.TryGetValueAsDreamObjectOfType(_objectTree.Matrix, out var matrixObject)) throw new Exception($"Value {node.Value} was not a matrix"); // Matrix3 except not really because DM matrix is actually 3x2 @@ -537,7 +539,7 @@ public Matrix3 Read(ISerializationManager serializationManager, DreamValueDataNo public ValidationNode Validate(ISerializationManager serializationManager, DreamValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context = null) { - if (node.Value.TryGetValueAsDreamObjectOfType(DreamPath.Matrix, out _)) + if (node.Value.TryGetValueAsDreamObjectOfType(_objectTree.Matrix, out _)) return new ValidatedValueNode(node); return new ErrorNode(node, $"Value {node.Value} is not a matrix"); diff --git a/OpenDreamRuntime/IDreamManager.cs b/OpenDreamRuntime/IDreamManager.cs index ea0977d86e..3bc2f97595 100644 --- a/OpenDreamRuntime/IDreamManager.cs +++ b/OpenDreamRuntime/IDreamManager.cs @@ -6,7 +6,6 @@ namespace OpenDreamRuntime { public interface IDreamManager { public bool Initialized { get; } public GameTick InitializedTick { get; } - public DreamObjectTree ObjectTree { get; } public DreamObject WorldInstance { get; } /// diff --git a/OpenDreamRuntime/Objects/DreamIcon.cs b/OpenDreamRuntime/Objects/DreamIcon.cs index 70ffce57ba..a2e09e8f27 100644 --- a/OpenDreamRuntime/Objects/DreamIcon.cs +++ b/OpenDreamRuntime/Objects/DreamIcon.cs @@ -302,8 +302,9 @@ public DreamIconOperationBlend(BlendType type, DreamValue blending, int xOffset, _xOffset = xOffset; _yOffset = yOffset; + var objectTree = IoCManager.Resolve(); var resourceManager = IoCManager.Resolve(); - (var blendingResource, _blendingDescription) = DreamMetaObjectIcon.GetIconResourceAndDescription(resourceManager, blending); + (var blendingResource, _blendingDescription) = DreamMetaObjectIcon.GetIconResourceAndDescription(objectTree, resourceManager, blending); _blending = resourceManager.LoadImage(blendingResource); if (_type is not BlendType.Overlay and not BlendType.Underlay) diff --git a/OpenDreamRuntime/Objects/DreamList.cs b/OpenDreamRuntime/Objects/DreamList.cs index 787d923f3b..169eb91c6b 100644 --- a/OpenDreamRuntime/Objects/DreamList.cs +++ b/OpenDreamRuntime/Objects/DreamList.cs @@ -22,7 +22,7 @@ public class DreamList : DreamObject { protected DreamList(int size = 0) : base(null) { _values = new List(size); - ObjectDefinition = _listDef ??= IoCManager.Resolve().ObjectTree.GetObjectDefinition(DreamPath.List); + ObjectDefinition = _listDef ??= IoCManager.Resolve().GetObjectDefinition(DreamPath.List); } public static DreamList CreateUninitialized(int size = 0) { @@ -248,6 +248,7 @@ public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth // global.vars list sealed class DreamGlobalVars : DreamList { [Dependency] private readonly IDreamManager _dreamMan = default!; + [Dependency] private readonly IDreamObjectTree _objectTree = default!; public override bool IsAssociative => true; // We don't use the associative array but, yes, we behave like an associative list @@ -262,7 +263,7 @@ public static DreamGlobalVars Create() { } public override List GetValues() { - var root = _dreamMan.ObjectTree.GetObjectDefinition(DreamPath.Root); + var root = _objectTree.GetObjectDefinition(DreamPath.Root); List values = new List(root.GlobalVariables.Keys.Count - 1); // Skip world foreach (var key in root.GlobalVariables.Keys.Skip(1)) { @@ -277,7 +278,7 @@ public override bool ContainsKey(DreamValue value) { return false; } - return _dreamMan.ObjectTree.GetObjectDefinition(DreamPath.Root).GlobalVariables.ContainsKey(varName); + return _objectTree.GetObjectDefinition(DreamPath.Root).GlobalVariables.ContainsKey(varName); } public override bool ContainsValue(DreamValue value) { @@ -289,7 +290,7 @@ public override DreamValue GetValue(DreamValue key) { throw new Exception($"Invalid var index {key}"); } - var root = _dreamMan.ObjectTree.GetObjectDefinition(DreamPath.Root); + var root = _objectTree.GetObjectDefinition(DreamPath.Root); if (!root.GlobalVariables.TryGetValue(varName, out var globalId)) { throw new Exception($"Invalid global {varName}"); } @@ -299,7 +300,7 @@ public override DreamValue GetValue(DreamValue key) { public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) { if (key.TryGetValueAsString(out var varName)) { - var root = _dreamMan.ObjectTree.GetObjectDefinition(DreamPath.Root); + var root = _objectTree.GetObjectDefinition(DreamPath.Root); if (!root.GlobalVariables.TryGetValue(varName, out var globalId)) { throw new Exception($"Cannot set value of undefined global \"{varName}\""); } @@ -314,16 +315,15 @@ public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth // atom.filters list // Operates on an atom's appearance public sealed class DreamFilterList : DreamList { - private readonly IDreamManager _dreamManager; - private readonly IAtomManager _atomManager; - private readonly ISerializationManager _serializationManager; + [Dependency] private readonly IDreamManager _dreamManager = default!; + [Dependency] private readonly IDreamObjectTree _objectTree = default!; + [Dependency] private readonly IAtomManager _atomManager = default!; + [Dependency] private readonly ISerializationManager _serializationManager = default!; private readonly DreamObject _atom; public DreamFilterList(DreamObject atom) { - _dreamManager = IoCManager.Resolve(); - _atomManager = IoCManager.Resolve(); - _serializationManager = IoCManager.Resolve(); + IoCManager.InjectDependencies(this); _atom = atom; } @@ -366,13 +366,13 @@ public override DreamValue GetValue(DreamValue key) { throw new Exception($"Atom only has {appearance.Filters.Count} filter(s), cannot index {filterIndex}"); DreamFilter filter = appearance.Filters[filterIndex - 1]; - DreamObject filterObject = _dreamManager.ObjectTree.CreateObject(DreamPath.Filter); + DreamObject filterObject = _objectTree.CreateObject(DreamPath.Filter); DreamMetaObjectFilter.DreamObjectToFilter[filterObject] = filter; return new DreamValue(filterObject); } public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) { - if (!value.TryGetValueAsDreamObjectOfType(DreamPath.Filter, out var filterObject)) + if (!value.TryGetValueAsDreamObjectOfType(_objectTree.Filter, out var filterObject)) throw new Exception($"Cannot set value of filter list to {value}"); if (!key.TryGetValueAsInteger(out var filterIndex) || filterIndex < 1) throw new Exception($"Invalid index into filter list: {key}"); @@ -382,7 +382,7 @@ public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth } public override void AddValue(DreamValue value) { - if (!value.TryGetValueAsDreamObjectOfType(DreamPath.Filter, out var filterObject)) + if (!value.TryGetValueAsDreamObjectOfType(_objectTree.Filter, out var filterObject)) throw new Exception($"Cannot add {value} to filter list"); DreamFilter filter = DreamMetaObjectFilter.DreamObjectToFilter[filterObject]; diff --git a/OpenDreamRuntime/Objects/DreamObject.cs b/OpenDreamRuntime/Objects/DreamObject.cs index a54a1ddf8c..128e5c109e 100644 --- a/OpenDreamRuntime/Objects/DreamObject.cs +++ b/OpenDreamRuntime/Objects/DreamObject.cs @@ -1,5 +1,4 @@ using OpenDreamRuntime.Procs; -using OpenDreamShared.Dream; using OpenDreamShared.Dream.Procs; using System.Globalization; @@ -45,8 +44,8 @@ public void SetObjectDefinition(DreamObjectDefinition objectDefinition) { _variables.Clear(); } - public bool IsSubtypeOf(DreamPath path) { - return ObjectDefinition.IsSubtypeOf(path); + public bool IsSubtypeOf(IDreamObjectTree.TreeEntry ancestor) { + return ObjectDefinition.IsSubtypeOf(ancestor); } public bool HasVariable(string name) { diff --git a/OpenDreamRuntime/Objects/DreamObjectDefinition.cs b/OpenDreamRuntime/Objects/DreamObjectDefinition.cs index 3149afa6c6..e8100a63e0 100644 --- a/OpenDreamRuntime/Objects/DreamObjectDefinition.cs +++ b/OpenDreamRuntime/Objects/DreamObjectDefinition.cs @@ -1,14 +1,12 @@ using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; using OpenDreamRuntime.Objects.MetaObjects; -using OpenDreamRuntime.Procs; using OpenDreamShared.Dream; namespace OpenDreamRuntime.Objects { - public sealed class DreamObjectDefinition - { + public sealed class DreamObjectDefinition { [Dependency] private readonly IDreamManager _dreamMan = default!; - public DreamPath Type; + public DreamPath Type => _treeNode.Path; + public DreamObjectDefinition? Parent => _treeNode.ParentEntry?.ObjectDefinition; public IDreamMetaObject MetaObject = null; public int? InitializationProc; public readonly Dictionary Procs = new(); @@ -16,20 +14,15 @@ public sealed class DreamObjectDefinition public readonly Dictionary Variables = new(); public readonly Dictionary GlobalVariables = new(); - private readonly DreamObjectDefinition? _parentObjectDefinition = null; - - public DreamObjectDefinition(DreamPath type) - { - IoCManager.InjectDependencies(this); - Type = type; - } + private readonly IDreamObjectTree _objectTree; + private readonly IDreamObjectTree.TreeEntry _treeNode; public DreamObjectDefinition(DreamObjectDefinition copyFrom) { IoCManager.InjectDependencies(this); - Type = copyFrom.Type; + _objectTree = copyFrom._objectTree; + _treeNode = copyFrom._treeNode; MetaObject = copyFrom.MetaObject; InitializationProc = copyFrom.InitializationProc; - _parentObjectDefinition = copyFrom._parentObjectDefinition; Variables = new Dictionary(copyFrom.Variables); GlobalVariables = new Dictionary(copyFrom.GlobalVariables); @@ -37,14 +30,16 @@ public DreamObjectDefinition(DreamObjectDefinition copyFrom) { OverridingProcs = new Dictionary(copyFrom.OverridingProcs); } - public DreamObjectDefinition(DreamPath type, DreamObjectDefinition parentObjectDefinition) { + public DreamObjectDefinition(IDreamObjectTree objectTree, IDreamObjectTree.TreeEntry treeNode) { IoCManager.InjectDependencies(this); - Type = type; - InitializationProc = parentObjectDefinition.InitializationProc; - _parentObjectDefinition = parentObjectDefinition; + _objectTree = objectTree; + _treeNode = treeNode; - Variables = new Dictionary(parentObjectDefinition.Variables); - GlobalVariables = new Dictionary(parentObjectDefinition.GlobalVariables); + if (Parent != null) { + InitializationProc = Parent.InitializationProc; + Variables = new Dictionary(Parent.Variables); + GlobalVariables = new Dictionary(Parent.GlobalVariables); + } } public void SetVariableDefinition(string variableName, DreamValue value) { @@ -54,7 +49,7 @@ public void SetVariableDefinition(string variableName, DreamValue value) { public void SetProcDefinition(string procName, int procId) { if (HasProc(procName)) { - var proc = _dreamMan.ObjectTree.Procs[procId]; + var proc = _objectTree.Procs[procId]; proc.SuperProc = GetProc(procName); OverridingProcs[procName] = procId; } else { @@ -62,17 +57,6 @@ public void SetProcDefinition(string procName, int procId) { } } - public void SetNativeProc(NativeProc.HandlerFn func) - { - var proc = _dreamMan.ObjectTree.CreateNativeProc(Type, func, out var procId); - SetProcDefinition(proc.Name, procId); - } - - public void SetNativeProc(Func> func) { - var proc = _dreamMan.ObjectTree.CreateAsyncNativeProc(Type, func, out var procId); - SetProcDefinition(proc.Name, procId); - } - public DreamProc GetProc(string procName) { if (TryGetProc(procName, out DreamProc? proc)) { return proc; @@ -82,17 +66,15 @@ public DreamProc GetProc(string procName) { } public bool TryGetProc(string procName, [NotNullWhen(true)] out DreamProc? proc) { - if (OverridingProcs.TryGetValue(procName, out var procId)) - { - proc = _dreamMan.ObjectTree.Procs[procId]; + if (OverridingProcs.TryGetValue(procName, out var procId)) { + proc = _objectTree.Procs[procId]; return true; } else if (Procs.TryGetValue(procName, out procId)) { - proc = _dreamMan.ObjectTree.Procs[procId]; + proc = _objectTree.Procs[procId]; return true; - } else if (_parentObjectDefinition != null) { - return _parentObjectDefinition.TryGetProc(procName, out proc); - } else - { + } else if (Parent != null) { + return Parent.TryGetProc(procName, out proc); + } else { proc = null; return false; } @@ -101,8 +83,8 @@ public bool TryGetProc(string procName, [NotNullWhen(true)] out DreamProc? proc) public bool HasProc(string procName) { if (Procs.ContainsKey(procName)) { return true; - } else if (_parentObjectDefinition != null) { - return _parentObjectDefinition.HasProc(procName); + } else if (Parent != null) { + return Parent.HasProc(procName); } else { return false; } @@ -115,17 +97,16 @@ public bool HasVariable(string variableName) { public bool TryGetVariable(string varName, out DreamValue value) { if (Variables.TryGetValue(varName, out value)) { return true; - } else if (_parentObjectDefinition != null) { - return _parentObjectDefinition.TryGetVariable(varName, out value); + } else if (Parent != null) { + return Parent.TryGetVariable(varName, out value); } else { return false; } } - public bool IsSubtypeOf(DreamPath path) { - if (Type.IsDescendantOf(path)) return true; - else if (_parentObjectDefinition != null) return _parentObjectDefinition.IsSubtypeOf(path); - else return false; + public bool IsSubtypeOf(IDreamObjectTree.TreeEntry ancestor) { + // Unsigned underflow is desirable here + return (_treeNode.TreeIndex - ancestor.TreeIndex) <= ancestor.ChildCount; } } } diff --git a/OpenDreamRuntime/Objects/DreamObjectTree.cs b/OpenDreamRuntime/Objects/DreamObjectTree.cs index ec07fbe306..21b67e49a3 100644 --- a/OpenDreamRuntime/Objects/DreamObjectTree.cs +++ b/OpenDreamRuntime/Objects/DreamObjectTree.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Threading.Tasks; @@ -9,25 +7,33 @@ using OpenDreamShared.Dream; using OpenDreamShared.Dream.Procs; using OpenDreamShared.Json; +using TreeEntry = OpenDreamRuntime.Objects.IDreamObjectTree.TreeEntry; namespace OpenDreamRuntime.Objects { - public sealed class DreamObjectTree { - public sealed class TreeEntry { - public DreamPath Path; - public readonly int Id; - public DreamObjectDefinition ObjectDefinition; - public TreeEntry ParentEntry; - public List InheritingTypes = new(); - - public TreeEntry(DreamPath path, int id) { - Path = path; - Id = id; - } - } - - public TreeEntry[] Types; - public List Procs; - public List Strings; //TODO: Store this somewhere else + public sealed class DreamObjectTree : IDreamObjectTree { + public TreeEntry[] Types { get; private set; } + public List Procs { get; private set; } + public List Strings { get; private set; } //TODO: Store this somewhere else + + public TreeEntry Root { get; private set; } + public TreeEntry World { get; private set; } + public TreeEntry Client { get; private set; } + public TreeEntry Datum { get; private set; } + public TreeEntry Sound { get; private set; } + public TreeEntry Matrix { get; private set; } + public TreeEntry Exception { get; private set; } + public TreeEntry Savefile { get; private set; } + public TreeEntry Regex { get; private set; } + public TreeEntry Filter { get; private set; } + public TreeEntry Icon { get; private set; } + public TreeEntry Image { get; private set; } + public TreeEntry MutableAppearance { get; private set; } + public TreeEntry Atom { get; private set; } + public TreeEntry Area { get; private set; } + public TreeEntry Turf { get; private set; } + public TreeEntry Movable { get; private set; } + public TreeEntry Obj { get; private set; } + public TreeEntry Mob { get; private set; } private Dictionary _pathToType = new(); private Dictionary _globalProcIds; @@ -173,12 +179,33 @@ private void LoadTypesFromJson(DreamTypeJson[] types) { //First pass: Create types and set them up for initialization for (int i = 0; i < Types.Length; i++) { DreamPath path = new DreamPath(types[i].Path); + var type = new TreeEntry(path, i); - Types[i] = new TreeEntry(path, i); - _pathToType[path] = Types[i]; + Types[i] = type; + _pathToType[path] = type; pathToTypeId[path] = i; } + Root = GetTreeEntry(DreamPath.Root); + World = GetTreeEntry(DreamPath.World); + Client = GetTreeEntry(DreamPath.Client); + Datum = GetTreeEntry(DreamPath.Datum); + Sound = GetTreeEntry(DreamPath.Sound); + Matrix = GetTreeEntry(DreamPath.Matrix); + Exception = GetTreeEntry(DreamPath.Exception); + Savefile = GetTreeEntry(DreamPath.Savefile); + Regex = GetTreeEntry(DreamPath.Regex); + Filter = GetTreeEntry(DreamPath.Filter); + Icon = GetTreeEntry(DreamPath.Icon); + Image = GetTreeEntry(DreamPath.Image); + MutableAppearance = GetTreeEntry(DreamPath.MutableAppearance); + Atom = GetTreeEntry(DreamPath.Atom); + Area = GetTreeEntry(DreamPath.Area); + Turf = GetTreeEntry(DreamPath.Turf); + Movable = GetTreeEntry(DreamPath.Movable); + Obj = GetTreeEntry(DreamPath.Obj); + Mob = GetTreeEntry(DreamPath.Mob); + //Second pass: Set each type's parent and children for (int i = 0; i < Types.Length; i++) { DreamTypeJson jsonType = types[i]; @@ -195,46 +222,43 @@ private void LoadTypesFromJson(DreamTypeJson[] types) { //Third pass: Load each type's vars and procs //This must happen top-down from the root of the object tree for inheritance to work //Thus, the enumeration of GetAllDescendants() + uint treeIndex = 0; foreach (TreeEntry type in GetAllDescendants(DreamPath.Root)) { int typeId = pathToTypeId[type.Path]; DreamTypeJson jsonType = types[typeId]; + var definition = new DreamObjectDefinition(this, type); - DreamObjectDefinition definition; - if (type.ParentEntry != null) { - definition = new DreamObjectDefinition(type.Path, type.ParentEntry.ObjectDefinition); - } else { - definition = new DreamObjectDefinition(type.Path); - } type.ObjectDefinition = definition; + type.TreeIndex = treeIndex++; LoadVariablesFromJson(definition, jsonType); - if (jsonType.Procs != null) - { - foreach (var procList in jsonType.Procs) - { - foreach (var procId in procList) - { + if (jsonType.Procs != null) { + foreach (var procList in jsonType.Procs) { + foreach (var procId in procList) { var proc = Procs[procId]; type.ObjectDefinition.SetProcDefinition(proc.Name, procId); } } } - if (jsonType.InitProc != null) - { + if (jsonType.InitProc != null) { var initProc = Procs[jsonType.InitProc.Value]; - if(definition.InitializationProc != null) + if (definition.InitializationProc != null) initProc.SuperProc = Procs[definition.InitializationProc.Value]; definition.InitializationProc = jsonType.InitProc.Value; } } - //Fourth pass: Set atom's text - foreach (TreeEntry type in GetAllDescendants(DreamPath.Atom)) - { - if (type.ObjectDefinition.Variables["text"].Equals(DreamValue.Null) && type.ObjectDefinition.Variables["name"].TryGetValueAsString(out var name)) - { + // Fourth pass: Set every TreeEntry's ChildrenCount + foreach (TreeEntry type in TraversePostOrder(Root)) { + if (type.ParentEntry != null) + type.ParentEntry.ChildCount += type.ChildCount + 1; + } + + //Fifth pass: Set atom's text + foreach (TreeEntry type in GetAllDescendants(DreamPath.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])); } } @@ -272,14 +296,13 @@ public DreamProc LoadProcJson(DreamTypeJson[] types, ProcDefinitionJson procDefi } DreamPath owningType = new DreamPath(types[procDefinition.OwningTypeId].Path); - var proc = new DMProc(owningType, procDefinition.Name, null, argumentNames, argumentTypes, bytecode, procDefinition.MaxStackSize, procDefinition.Attributes, procDefinition.VerbName, procDefinition.VerbCategory, procDefinition.VerbDesc, procDefinition.Invisibility); + var proc = new DMProc(owningType, procDefinition.Name, null, argumentNames, argumentTypes, bytecode, procDefinition.MaxStackSize, procDefinition.Attributes, procDefinition.VerbName, procDefinition.VerbCategory, procDefinition.VerbDesc, procDefinition.Invisibility, this); proc.Source = procDefinition.Source; proc.Line = procDefinition.Line; return proc; } - private void LoadProcsFromJson(DreamTypeJson[] types, ProcDefinitionJson[] jsonProcs, List jsonGlobalProcs) - { + private void LoadProcsFromJson(DreamTypeJson[] types, ProcDefinitionJson[] jsonProcs, List jsonGlobalProcs) { Procs = new(jsonProcs.Length); foreach (var proc in jsonProcs) { @@ -297,8 +320,7 @@ private void LoadProcsFromJson(DreamTypeJson[] types, ProcDefinitionJson[] jsonP } } - public NativeProc CreateNativeProc(DreamPath owningType, NativeProc.HandlerFn func, out int procId) - { + public NativeProc CreateNativeProc(DreamPath owningType, NativeProc.HandlerFn func, out int procId) { var (name, defaultArgumentValues, argumentNames) = NativeProc.GetNativeInfo(func); var proc = new NativeProc(owningType, name, null, argumentNames, null, defaultArgumentValues, func, null, null, null, null); procId = Procs.Count; @@ -306,8 +328,7 @@ public NativeProc CreateNativeProc(DreamPath owningType, NativeProc.HandlerFn fu return proc; } - public AsyncNativeProc CreateAsyncNativeProc(DreamPath owningType, Func> func, out int procId) - { + public AsyncNativeProc CreateAsyncNativeProc(DreamPath owningType, Func> func, out int procId) { var (name, defaultArgumentValues, argumentNames) = NativeProc.GetNativeInfo(func); var proc = new AsyncNativeProc(owningType, name, null, argumentNames, null, defaultArgumentValues, func,null, null, null, null); procId = Procs.Count; @@ -328,5 +349,98 @@ public void SetGlobalNativeProc(Func> fu Procs[_globalProcIds[name]] = proc; } + + public void SetNativeProc(DreamObjectDefinition definition, NativeProc.HandlerFn func) { + var proc = CreateNativeProc(definition.Type, func, out var procId); + + definition.SetProcDefinition(proc.Name, procId); + } + + public void SetNativeProc(DreamObjectDefinition definition, Func> func) { + var proc = CreateAsyncNativeProc(definition.Type, func, out var procId); + + definition.SetProcDefinition(proc.Name, procId); + } + + /// + /// Enumerate the inheritance tree in post-order + /// + private IEnumerable TraversePostOrder(TreeEntry from) { + foreach (int typeId in from.InheritingTypes) { + TreeEntry type = Types[typeId]; + IEnumerator typeChildren = TraversePostOrder(type).GetEnumerator(); + + while (typeChildren.MoveNext()) yield return typeChildren.Current; + } + + yield return from; + } + } + + public interface IDreamObjectTree { + public sealed class TreeEntry { + public DreamPath Path; + public readonly int Id; + public DreamObjectDefinition ObjectDefinition; + public TreeEntry ParentEntry; + public List InheritingTypes = new(); + + /// + /// This node's index in the inheritance tree based on a depth-first search
+ /// Useful for quickly determining inheritance + ///
+ public uint TreeIndex; + + /// + /// The total amount of children this node has + /// + public uint ChildCount; + + public TreeEntry(DreamPath path, int id) { + Path = path; + Id = id; + } + } + + public TreeEntry[] Types { get; } + public List Procs { get; } + public List Strings { get; } + + // All the built-in types + public TreeEntry World { get; } + public TreeEntry Client { get; } + public TreeEntry Datum { get; } + public TreeEntry Sound { get; } + public TreeEntry Matrix { get; } + public TreeEntry Exception { get; } + public TreeEntry Savefile { get; } + public TreeEntry Regex { get; } + public TreeEntry Filter { get; } + public TreeEntry Icon { get; } + public TreeEntry Image { get; } + public TreeEntry MutableAppearance { get; } + public TreeEntry Atom { get; } + public TreeEntry Area { get; } + public TreeEntry Turf { get; } + public TreeEntry Movable { get; } + public TreeEntry Obj { get; } + public TreeEntry Mob { get; } + + public void LoadJson(DreamCompiledJson json); + public void SetMetaObject(DreamPath path, IDreamMetaObject metaObject); + public void SetGlobalNativeProc(NativeProc.HandlerFn func); + public void SetGlobalNativeProc(Func> func); + public void SetNativeProc(DreamObjectDefinition definition, NativeProc.HandlerFn func); + public void SetNativeProc(DreamObjectDefinition definition, Func> func); + + public DreamObject CreateObject(DreamPath path); + public bool TryGetGlobalProc(string name, [NotNullWhen(true)] out DreamProc? globalProc); + public bool HasTreeEntry(DreamPath path); + public TreeEntry GetTreeEntry(DreamPath path); + public TreeEntry GetTreeEntry(int typeId); + public DreamObjectDefinition GetObjectDefinition(DreamPath path); + public DreamObjectDefinition GetObjectDefinition(int typeId); + public IEnumerable GetAllDescendants(DreamPath path); + public DreamValue GetDreamValueFromJsonElement(object value); } } diff --git a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectArea.cs b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectArea.cs index 21cf10e406..0eeca97316 100644 --- a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectArea.cs +++ b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectArea.cs @@ -7,6 +7,7 @@ sealed class DreamMetaObjectArea : IDreamMetaObject { public IDreamMetaObject? ParentType { get; set; } [Dependency] private readonly IDreamManager _dreamManager = default!; + [Dependency] private readonly IDreamObjectTree _objectTree = default!; [Dependency] private readonly IDreamMapManager _dreamMapManager = default!; public DreamMetaObjectArea() { @@ -17,7 +18,7 @@ public void OnObjectCreated(DreamObject dreamObject, DreamProcArguments creation DreamList contents = DreamList.Create(); contents.ValueAssigned += (_, _, value) => { - if (!value.TryGetValueAsDreamObjectOfType(DreamPath.Turf, out DreamObject turf)) + if (!value.TryGetValueAsDreamObjectOfType(_objectTree.Turf, out DreamObject turf)) return; (Vector2i pos, DreamMapManager.Level level) = _dreamMapManager.GetTurfPosition(turf); diff --git a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectAtom.cs b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectAtom.cs index baf851857f..6cabf6d001 100644 --- a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectAtom.cs +++ b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectAtom.cs @@ -9,6 +9,7 @@ sealed class DreamMetaObjectAtom : IDreamMetaObject { public IDreamMetaObject? ParentType { get; set; } [Dependency] private readonly IDreamManager _dreamManager = default!; + [Dependency] private readonly IDreamObjectTree _objectTree = default!; [Dependency] private readonly IAtomManager _atomManager = default!; private readonly Dictionary _filterLists = new(); @@ -19,7 +20,7 @@ public DreamMetaObjectAtom() { public void OnObjectCreated(DreamObject dreamObject, DreamProcArguments creationArguments) { // Turfs can be new()ed multiple times, so let DreamMapManager handle it. - if (!dreamObject.IsSubtypeOf(DreamPath.Turf)) { + if (!dreamObject.IsSubtypeOf(_objectTree.Turf)) { _dreamManager.WorldContentsList.AddValue(new DreamValue(dreamObject)); } @@ -47,7 +48,7 @@ public void OnVariableSet(DreamObject dreamObject, string varName, DreamValue va _atomManager.UpdateAppearance(dreamObject, appearance => { if (value.TryGetValueAsDreamResource(out var resource)) { appearance.Icon = resource.Id; - } else if (value.TryGetValueAsDreamObjectOfType(DreamPath.Icon, out var iconObject)) { + } else if (value.TryGetValueAsDreamObjectOfType(_objectTree.Icon, out var iconObject)) { DreamIcon icon = DreamMetaObjectIcon.ObjectToDreamIcon[iconObject]; (resource, _) = icon.GenerateDMI(); @@ -118,7 +119,7 @@ public void OnVariableSet(DreamObject dreamObject, string varName, DreamValue va case "transform": { _atomManager.UpdateAppearance(dreamObject, appearance => { - float[] matrixArray = value.TryGetValueAsDreamObjectOfType(DreamPath.Matrix, out var matrix) + float[] matrixArray = value.TryGetValueAsDreamObjectOfType(_objectTree.Matrix, out var matrix) ? DreamMetaObjectMatrix.MatrixToTransformFloatArray(matrix) : DreamMetaObjectMatrix.IdentityMatrixArray; @@ -187,7 +188,7 @@ public DreamValue OnVariableGet(DreamObject dreamObject, string varName, DreamVa switch (varName) { case "transform": // Clone the matrix - DreamObject matrix = _dreamManager.ObjectTree.CreateObject(DreamPath.Matrix); + DreamObject matrix = _objectTree.CreateObject(DreamPath.Matrix); matrix.InitSpawn(new DreamProcArguments(new() { value })); return new DreamValue(matrix); @@ -204,7 +205,7 @@ private IconAppearance CreateOverlayAppearance(DreamObject atom, DreamValue valu if (value.TryGetValueAsString(out string valueString)) { appearance.Icon = _atomManager.GetAppearance(atom)?.Icon; appearance.IconState = valueString; - } else if (value.TryGetValueAsDreamObjectOfType(DreamPath.MutableAppearance, out var mutableAppearance)) { + } else if (value.TryGetValueAsDreamObjectOfType(_objectTree.MutableAppearance, out var mutableAppearance)) { DreamValue icon = mutableAppearance.GetVariable("icon"); if (icon.TryGetValueAsDreamResource(out var iconResource)) { appearance.Icon = iconResource.Id; @@ -223,7 +224,7 @@ private IconAppearance CreateOverlayAppearance(DreamObject atom, DreamValue valu mutableAppearance.GetVariable("layer").TryGetValueAsFloat(out appearance.Layer); mutableAppearance.GetVariable("pixel_x").TryGetValueAsInteger(out appearance.PixelOffset.X); mutableAppearance.GetVariable("pixel_y").TryGetValueAsInteger(out appearance.PixelOffset.Y); - } else if (value.TryGetValueAsDreamObjectOfType(DreamPath.Image, out var image)) { + } else if (value.TryGetValueAsDreamObjectOfType(_objectTree.Image, out var image)) { DreamValue icon = image.GetVariable("icon"); DreamValue iconState = image.GetVariable("icon_state"); @@ -240,7 +241,7 @@ private IconAppearance CreateOverlayAppearance(DreamObject atom, DreamValue valu image.GetVariable("layer").TryGetValueAsFloat(out appearance.Layer); image.GetVariable("pixel_x").TryGetValueAsInteger(out appearance.PixelOffset.X); image.GetVariable("pixel_y").TryGetValueAsInteger(out appearance.PixelOffset.Y); - } else if (value.TryGetValueAsDreamObjectOfType(DreamPath.Icon, out var icon)) { + } else if (value.TryGetValueAsDreamObjectOfType(_objectTree.Icon, out var icon)) { var iconObj = DreamMetaObjectIcon.ObjectToDreamIcon[icon]; var (resource, dmiDescription) = iconObj.GenerateDMI(); @@ -248,10 +249,10 @@ private IconAppearance CreateOverlayAppearance(DreamObject atom, DreamValue valu appearance.Icon = resource.Id; appearance.IconState = dmiDescription.GetStateOrDefault(iconState)?.Name; - } else if (value.TryGetValueAsDreamObjectOfType(DreamPath.Atom, out var overlayAtom)) { + } else if (value.TryGetValueAsDreamObjectOfType(_objectTree.Atom, out var overlayAtom)) { appearance = _atomManager.CreateAppearanceFromAtom(overlayAtom); } else if (value.TryGetValueAsPath(out DreamPath path)) { - var def = _dreamManager.ObjectTree.GetObjectDefinition(path); + var def = _objectTree.GetObjectDefinition(path); appearance = _atomManager.CreateAppearanceFromDefinition(def); } else { throw new Exception($"Invalid overlay {value}"); diff --git a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectClient.cs b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectClient.cs index c3cff42f6e..0e4e11355e 100644 --- a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectClient.cs +++ b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectClient.cs @@ -11,7 +11,12 @@ sealed class DreamMetaObjectClient : IDreamMetaObject { private readonly Dictionary _screenListToClient = new(); - private readonly IDreamManager _dreamManager = IoCManager.Resolve(); + [Dependency] private readonly IDreamManager _dreamManager = default!; + [Dependency] private readonly IDreamObjectTree _objectTree = default!; + + public DreamMetaObjectClient() { + IoCManager.InjectDependencies(this); + } public void OnObjectCreated(DreamObject dreamObject, DreamProcArguments creationArguments) { ParentType?.OnObjectCreated(dreamObject, creationArguments); @@ -137,7 +142,7 @@ public DreamValue OnVariableGet(DreamObject dreamObject, string varName, DreamVa } public void OperatorOutput(DreamValue a, DreamValue b) { - if (!a.TryGetValueAsDreamObjectOfType(DreamPath.Client, out var client)) + if (!a.TryGetValueAsDreamObjectOfType(_objectTree.Client, out var client)) throw new ArgumentException($"Left-hand value was not the expected type {DreamPath.Client}"); DreamConnection connection = _dreamManager.GetConnectionFromClient(client); @@ -145,7 +150,7 @@ public void OperatorOutput(DreamValue a, DreamValue b) { } private void ScreenValueAssigned(DreamList screenList, DreamValue screenKey, DreamValue screenValue) { - if (!screenValue.TryGetValueAsDreamObjectOfType(DreamPath.Movable, out var movable)) + if (!screenValue.TryGetValueAsDreamObjectOfType(_objectTree.Movable, out var movable)) return; DreamConnection connection = _dreamManager.GetConnectionFromClient(_screenListToClient[screenList]); @@ -153,7 +158,7 @@ private void ScreenValueAssigned(DreamList screenList, DreamValue screenKey, Dre } private void ScreenBeforeValueRemoved(DreamList screenList, DreamValue screenKey, DreamValue screenValue) { - if (!screenValue.TryGetValueAsDreamObjectOfType(DreamPath.Movable, out var movable)) + if (!screenValue.TryGetValueAsDreamObjectOfType(_objectTree.Movable, out var movable)) return; DreamConnection connection = _dreamManager.GetConnectionFromClient(_screenListToClient[screenList]); diff --git a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectDatum.cs b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectDatum.cs index 760d5a511e..f6654d4048 100644 --- a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectDatum.cs +++ b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectDatum.cs @@ -8,11 +8,16 @@ sealed class DreamMetaObjectDatum : IDreamMetaObject { public bool ShouldCallNew => true; public IDreamMetaObject? ParentType { get; set; } - private readonly IDreamManager _dreamManager = IoCManager.Resolve(); + [Dependency] private readonly IDreamManager _dreamManager = default!; + [Dependency] private readonly IDreamObjectTree _objectTree = default!; + + public DreamMetaObjectDatum() { + IoCManager.InjectDependencies(this); + } public void OnObjectCreated(DreamObject dreamObject, DreamProcArguments creationArguments) { - if (!dreamObject.IsSubtypeOf(DreamPath.Atom)) // Atoms are in world.contents - { + // Atoms are in world.contents + if (!dreamObject.IsSubtypeOf(_objectTree.Atom)) { _dreamManager.Datums.Add(dreamObject); } @@ -22,8 +27,8 @@ public void OnObjectCreated(DreamObject dreamObject, DreamProcArguments creation public void OnObjectDeleted(DreamObject dreamObject) { ParentType?.OnObjectDeleted(dreamObject); - if (!dreamObject.IsSubtypeOf(DreamPath.Atom)) // Atoms are in world.contents - { + // Atoms are in world.contents + if (!dreamObject.IsSubtypeOf(_objectTree.Atom)) { _dreamManager.Datums.Remove(dreamObject); } @@ -36,7 +41,7 @@ public DreamValue OnVariableGet(DreamObject dreamObject, string varName, DreamVa return varName switch { "type" => new DreamValue(dreamObject.ObjectDefinition.Type), - "parent_type" => new DreamValue(_dreamManager.ObjectTree.GetTreeEntry(dreamObject.ObjectDefinition.Type) + "parent_type" => new DreamValue(_objectTree.GetTreeEntry(dreamObject.ObjectDefinition.Type) .ParentEntry.ObjectDefinition.Type), "vars" => new DreamValue(DreamListVars.Create(dreamObject)), _ => ParentType?.OnVariableGet(dreamObject, varName, value) ?? value diff --git a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectIcon.cs b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectIcon.cs index 1267fbe425..570a82f466 100644 --- a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectIcon.cs +++ b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectIcon.cs @@ -12,6 +12,7 @@ sealed class DreamMetaObjectIcon : IDreamMetaObject { public IDreamMetaObject? ParentType { get; set; } [Dependency] private readonly DreamResourceManager _rscMan = default!; + [Dependency] private readonly IDreamObjectTree _objectTree = default!; public DreamMetaObjectIcon() { IoCManager.InjectDependencies(this); @@ -34,7 +35,7 @@ public void OnObjectCreated(DreamObject dreamObject, DreamProcArguments creation if (icon != DreamValue.Null) { // TODO: Could maybe have an alternative path for /icon values so the DMI doesn't have to be generated - var (iconRsc, iconDescription) = GetIconResourceAndDescription(_rscMan, icon); + var (iconRsc, iconDescription) = GetIconResourceAndDescription(_objectTree, _rscMan, icon); dreamIcon.InsertStates(iconRsc, iconDescription, state, dir, frame, useStateName: false); } @@ -61,8 +62,8 @@ public void OnVariableSet(DreamObject dreamObject, string varName, DreamValue va } public static (DreamResource Resource, ParsedDMIDescription Description) GetIconResourceAndDescription( - DreamResourceManager resourceManager, DreamValue value) { - if (value.TryGetValueAsDreamObjectOfType(DreamPath.Icon, out var iconObj)) { + IDreamObjectTree objectTree, DreamResourceManager resourceManager, DreamValue value) { + if (value.TryGetValueAsDreamObjectOfType(objectTree.Icon, out var iconObj)) { DreamIcon dreamIcon = ObjectToDreamIcon[iconObj]; return dreamIcon.GenerateDMI(); diff --git a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectMatrix.cs b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectMatrix.cs index 25ad3db2c0..4c4c73232f 100644 --- a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectMatrix.cs +++ b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectMatrix.cs @@ -7,15 +7,17 @@ sealed class DreamMetaObjectMatrix : IDreamMetaObject { public bool ShouldCallNew => true; public IDreamMetaObject? ParentType { get; set; } - private readonly IDreamManager _dreamManager = IoCManager.Resolve(); + [Dependency] private readonly IDreamManager _dreamManager = default!; + [Dependency] private readonly IDreamObjectTree _objectTree = default!; + + public DreamMetaObjectMatrix() { + IoCManager.InjectDependencies(this); + } /// Used to create a float array understandable by to be a transform. /// The matrix's values in an array, in [a,d,b,e,c,f] order. - /// Thrown when matrix is invalid. + /// This will not verify that this is a /matrix public static float[] MatrixToTransformFloatArray(DreamObject matrix) { - if (!matrix.IsSubtypeOf(DreamPath.Matrix)) - throw new ArgumentException($"Invalid matrix {matrix}"); - float[] array = new float[6]; matrix.GetVariable("a").TryGetValueAsFloat(out array[0]); matrix.GetVariable("d").TryGetValueAsFloat(out array[1]); @@ -30,11 +32,8 @@ public static float[] MatrixToTransformFloatArray(DreamObject matrix) { /// Used when printing this matrix to enumerate its values in order. ///
/// The matrix's values in [a,b,c,d,e,f] order. - /// Thrown when matrix is invalid. + /// This will not verify that this is a /matrix public static IEnumerable EnumerateMatrix(DreamObject matrix) { - if (!matrix.IsSubtypeOf(DreamPath.Matrix)) - throw new ArgumentException($"Invalid matrix {matrix}"); - float ret = 0f; matrix.GetVariable("a").TryGetValueAsFloat(out ret); yield return ret; @@ -51,7 +50,7 @@ public static IEnumerable EnumerateMatrix(DreamObject matrix) { } public DreamValue OperatorMultiply(DreamValue a, DreamValue b) { - if (!a.TryGetValueAsDreamObjectOfType(DreamPath.Matrix, out DreamObject left)) + if (!a.TryGetValueAsDreamObjectOfType(_objectTree.Matrix, out DreamObject left)) throw new ArgumentException($"Invalid matrix {a}"); left.GetVariable("a").TryGetValueAsFloat(out float lA); @@ -62,7 +61,7 @@ public DreamValue OperatorMultiply(DreamValue a, DreamValue b) { left.GetVariable("f").TryGetValueAsFloat(out float lF); if (b.TryGetValueAsFloat(out float bFloat)) { - DreamObject output = _dreamManager.ObjectTree.CreateObject(DreamPath.Matrix); + DreamObject output = _objectTree.CreateObject(DreamPath.Matrix); output.SetVariable("a", new(lA * bFloat)); output.SetVariable("b", new(lB * bFloat)); output.SetVariable("c", new(lC * bFloat)); @@ -71,7 +70,7 @@ public DreamValue OperatorMultiply(DreamValue a, DreamValue b) { output.SetVariable("f", new(lF * bFloat)); return new(output); - } else if (b.TryGetValueAsDreamObjectOfType(DreamPath.Matrix, out DreamObject right)) { + } else if (b.TryGetValueAsDreamObjectOfType(_objectTree.Matrix, out DreamObject right)) { right.GetVariable("a").TryGetValueAsFloat(out float rA); right.GetVariable("b").TryGetValueAsFloat(out float rB); right.GetVariable("c").TryGetValueAsFloat(out float rC); @@ -79,7 +78,7 @@ public DreamValue OperatorMultiply(DreamValue a, DreamValue b) { right.GetVariable("e").TryGetValueAsFloat(out float rE); right.GetVariable("f").TryGetValueAsFloat(out float rF); - DreamObject output = _dreamManager.ObjectTree.CreateObject(DreamPath.Matrix); + DreamObject output = _objectTree.CreateObject(DreamPath.Matrix); output.SetVariable("a", new(rA * lA + rD * lB)); output.SetVariable("b", new(rB * lA + rE * lB)); output.SetVariable("c", new(rC * lA + rF * lB + lC)); @@ -97,7 +96,7 @@ public DreamValue OperatorMultiply(DreamValue a, DreamValue b) { } public DreamValue OperatorEquivalent(DreamValue a, DreamValue b) { - if (a.TryGetValueAsDreamObjectOfType(DreamPath.Matrix, out DreamObject? left) && b.TryGetValueAsDreamObjectOfType(DreamPath.Matrix, out DreamObject? right)) { + if (a.TryGetValueAsDreamObjectOfType(_objectTree.Matrix, out DreamObject? left) && b.TryGetValueAsDreamObjectOfType(_objectTree.Matrix, out DreamObject? right)) { const string elements = "abcdef"; for (int i = 0; i < elements.Length; i++) { left.GetVariable(elements[i].ToString()).TryGetValueAsFloat(out var leftValue); // sets leftValue to 0 if this isn't a float diff --git a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectMob.cs b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectMob.cs index 5943b9f659..3ce27882c8 100644 --- a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectMob.cs +++ b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectMob.cs @@ -8,6 +8,7 @@ sealed class DreamMetaObjectMob : IDreamMetaObject { public IDreamMetaObject? ParentType { get; set; } [Dependency] private readonly IDreamManager _dreamManager = default!; + [Dependency] private readonly IDreamObjectTree _objectTree = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; public DreamMetaObjectMob() { @@ -49,7 +50,7 @@ public void OnVariableSet(DreamObject dreamObject, string varName, DreamValue va public DreamValue OnVariableGet(DreamObject dreamObject, string varName, DreamValue value) { if (varName == "key" || varName == "ckey") { - if (dreamObject.GetVariable("client").TryGetValueAsDreamObjectOfType(DreamPath.Client, out var client)) { + if (dreamObject.GetVariable("client").TryGetValueAsDreamObjectOfType(_objectTree.Client, out var client)) { return client.GetVariable(varName); } else { return DreamValue.Null; @@ -62,9 +63,9 @@ public DreamValue OnVariableGet(DreamObject dreamObject, string varName, DreamVa } public void OperatorOutput(DreamValue a, DreamValue b) { - if (!a.TryGetValueAsDreamObjectOfType(DreamPath.Mob, out var mob)) + if (!a.TryGetValueAsDreamObjectOfType(_objectTree.Mob, out var mob)) throw new ArgumentException($"Left-hand value was not the expected type {DreamPath.Mob}"); - if (!mob.GetVariable("client").TryGetValueAsDreamObjectOfType(DreamPath.Client, out var client)) + if (!mob.GetVariable("client").TryGetValueAsDreamObjectOfType(_objectTree.Client, out var client)) throw new Exception($"Failed to get client from {mob}"); DreamConnection connection = _dreamManager.GetConnectionFromClient(client); diff --git a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectMovable.cs b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectMovable.cs index d661d99ccf..d6bbe619a5 100644 --- a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectMovable.cs +++ b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectMovable.cs @@ -10,6 +10,7 @@ class DreamMetaObjectMovable : IDreamMetaObject { public IDreamMetaObject? ParentType { get; set; } [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IDreamObjectTree _objectTree = default!; [Dependency] private readonly IDreamMapManager _dreamMapManager = default!; [Dependency] private readonly IAtomManager _atomManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; @@ -22,7 +23,7 @@ public void OnObjectCreated(DreamObject dreamObject, DreamProcArguments creation ParentType?.OnObjectCreated(dreamObject, creationArguments); DreamValue locArgument = creationArguments.GetArgument(0, "loc"); - if (locArgument.TryGetValueAsDreamObjectOfType(DreamPath.Atom, out _)) { + if (locArgument.TryGetValueAsDreamObjectOfType(_objectTree.Atom, out _)) { dreamObject.SetVariable("loc", locArgument); //loc is set before /New() is ever called } @@ -54,11 +55,11 @@ public void OnVariableSet(DreamObject dreamObject, string varName, DreamValue va if (!_entityManager.TryGetComponent(entity, out var transform)) return; - if (value.TryGetValueAsDreamObjectOfType(DreamPath.Turf, out var turfLoc)) { + if (value.TryGetValueAsDreamObjectOfType(_objectTree.Turf, out var turfLoc)) { (Vector2i pos, DreamMapManager.Level level) = _dreamMapManager.GetTurfPosition(turfLoc); transform.AttachParent(level.Grid.Owner); transform.WorldPosition = pos; - } else if (value.TryGetValueAsDreamObjectOfType(DreamPath.Movable, out var movableLoc)) { + } else if (value.TryGetValueAsDreamObjectOfType(_objectTree.Movable, out var movableLoc)) { EntityUid locEntity = _atomManager.GetMovableEntity(movableLoc); transform.AttachParent(locEntity); transform.LocalPosition = Vector2.Zero; diff --git a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectRegex.cs b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectRegex.cs index 8fd306816d..bc013cfd56 100644 --- a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectRegex.cs +++ b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectRegex.cs @@ -9,6 +9,12 @@ sealed class DreamMetaObjectRegex : IDreamMetaObject { public bool ShouldCallNew => false; public IDreamMetaObject? ParentType { get; set; } + [Dependency] private readonly IDreamObjectTree _objectTree = default!; + + public DreamMetaObjectRegex() { + IoCManager.InjectDependencies(this); + } + public struct DreamRegex { public Regex Regex; public bool IsGlobal; @@ -19,7 +25,7 @@ public void OnObjectCreated(DreamObject dreamObject, DreamProcArguments creation DreamValue flags = creationArguments.GetArgument(1, "flags"); DreamRegex regex; - if (pattern.TryGetValueAsDreamObjectOfType(DreamPath.Regex, out DreamObject copyFrom)) { + if (pattern.TryGetValueAsDreamObjectOfType(_objectTree.Regex, out DreamObject copyFrom)) { regex = ObjectToDreamRegex[copyFrom]; } else if (pattern.TryGetValueAsString(out string patternString)) { regex = new DreamRegex(); diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index e1c14d8f8a..b5ff8ff493 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -60,7 +60,7 @@ static class DMOpcodeHandlers { return null; } - private static IEnumerable GetEnumerableContents(DreamValue value) { + private static DreamValue[] GetEnumerableContents(IDreamObjectTree objectTree, DreamValue value) { DreamList? list = null; if (value.TryGetValueAsDreamObject(out var listObject)) list = listObject as DreamList; @@ -68,31 +68,29 @@ private static IEnumerable GetEnumerableContents(DreamValue value) { if (list == null) { if (listObject == null) { list = null; - } else if (listObject.IsSubtypeOf(DreamPath.Atom) || listObject.IsSubtypeOf(DreamPath.World)) { + } else if (listObject.IsSubtypeOf(objectTree.Atom) || listObject.IsSubtypeOf(objectTree.World)) { list = listObject.GetVariable("contents").GetValueAsDreamList(); } else { throw new Exception($"Object {listObject} is not a {DreamPath.List}, {DreamPath.Atom} or {DreamPath.World}"); } } - return list?.GetValues() ?? Enumerable.Empty(); + return list?.GetValues().ToArray() ?? Array.Empty(); } public static ProcStatus? CreateListEnumerator(DMProcState state) { - var contents = GetEnumerableContents(state.Pop()); - var values = new List(contents); + var contents = GetEnumerableContents(state.Proc.ObjectTree, state.Pop()); - state.EnumeratorStack.Push(values.GetEnumerator()); + state.EnumeratorStack.Push(new DreamValueArrayEnumerator(contents)); return null; } public static ProcStatus? CreateFilteredListEnumerator(DMProcState state) { - var contents = GetEnumerableContents(state.Pop()); + var contents = GetEnumerableContents(state.Proc.ObjectTree, state.Pop()); var filterTypeId = state.ReadInt(); - var filterType = state.DreamManager.ObjectTree.GetTreeEntry(filterTypeId).Path; - var values = new List(contents); + var filterType = state.Proc.ObjectTree.GetTreeEntry(filterTypeId); - state.EnumeratorStack.Push(new DreamValueAsObjectEnumerator(values, filterType)); + state.EnumeratorStack.Push(new FilteredDreamValueArrayEnumerator(contents, filterType)); return null; } @@ -106,13 +104,15 @@ private static IEnumerable GetEnumerableContents(DreamValue value) { return null; } - if (state.DreamManager.ObjectTree.GetObjectDefinition(type).IsSubtypeOf(DreamPath.Atom)) { - state.EnumeratorStack.Push( - new DreamValueAsObjectEnumerator(state.DreamManager.WorldContentsList.GetValues(), type)); + if (state.Proc.ObjectTree.GetObjectDefinition(type).IsSubtypeOf(state.Proc.ObjectTree.Atom)) { + var filterType = state.Proc.ObjectTree.GetTreeEntry(type); + var worldContents = state.DreamManager.WorldContentsList.GetValues().ToArray(); // TODO: Remove the ToArray() + + state.EnumeratorStack.Push(new FilteredDreamValueArrayEnumerator(worldContents, filterType)); return null; } - if (state.DreamManager.ObjectTree.GetObjectDefinition(type).IsSubtypeOf(DreamPath.Datum)) { + if (state.Proc.ObjectTree.GetObjectDefinition(type).IsSubtypeOf(state.Proc.ObjectTree.Datum)) { state.EnumeratorStack.Push(new DreamObjectEnumerator(state.DreamManager.Datums)); return null; } @@ -125,35 +125,30 @@ private static IEnumerable GetEnumerableContents(DreamValue value) { float rangeEnd = state.Pop().GetValueAsFloat(); float rangeStart = state.Pop().GetValueAsFloat(); - state.EnumeratorStack.Push(new DreamProcRangeEnumerator(rangeStart, rangeEnd, step)); + state.EnumeratorStack.Push(new DreamValueRangeEnumerator(rangeStart, rangeEnd, step)); return null; } public static ProcStatus? CreateObject(DMProcState state) { DreamProcArguments arguments = state.PopArguments(); var val = state.Pop(); - if (!val.TryGetValueAsPath(out var objectPath)) - { - if (val.TryGetValueAsString(out var pathString)) - { + if (!val.TryGetValueAsPath(out var objectPath)) { + if (val.TryGetValueAsString(out var pathString)) { objectPath = new DreamPath(pathString); - if (!state.DreamManager.ObjectTree.HasTreeEntry(objectPath)) - { + if (!state.Proc.ObjectTree.HasTreeEntry(objectPath)) { throw new Exception($"Cannot create unknown object {val.Value}"); } - } - else - { + } else { throw new Exception($"Cannot create object from invalid type {val}"); } } - DreamObjectDefinition objectDef = state.DreamManager.ObjectTree.GetObjectDefinition(objectPath); - if (objectDef.IsSubtypeOf(DreamPath.Turf)) { + DreamObjectDefinition objectDef = state.Proc.ObjectTree.GetObjectDefinition(objectPath); + if (objectDef.IsSubtypeOf(state.Proc.ObjectTree.Turf)) { // Turfs are special. They're never created outside of map initialization // So instead this will replace an existing turf's type and return that same turf DreamValue loc = arguments.GetArgument(0, "loc"); - if (!loc.TryGetValueAsDreamObjectOfType(DreamPath.Turf, out var turf)) + if (!loc.TryGetValueAsDreamObjectOfType(state.Proc.ObjectTree.Turf, out var turf)) throw new Exception($"Invalid turf loc {loc}"); IDreamMapManager dreamMapManager = IoCManager.Resolve(); @@ -163,7 +158,7 @@ private static IEnumerable GetEnumerableContents(DreamValue value) { return null; } - DreamObject newObject = state.DreamManager.ObjectTree.CreateObject(objectPath); + DreamObject newObject = state.Proc.ObjectTree.CreateObject(objectPath); state.Thread.PushProcState(newObject.InitProc(state.Thread, state.Usr, arguments)); return ProcStatus.Called; } @@ -174,7 +169,7 @@ private static IEnumerable GetEnumerableContents(DreamValue value) { } public static ProcStatus? Enumerate(DMProcState state) { - IEnumerator enumerator = state.EnumeratorStack.Peek(); + IDreamValueEnumerator enumerator = state.EnumeratorStack.Peek(); DMReference reference = state.ReadReference(); int jumpToIfFailure = state.ReadInt(); bool successfulEnumeration = enumerator.MoveNext(); @@ -393,7 +388,7 @@ private static void HandleSuffixPronoun(ref StringBuilder formattedString, ReadO if (owner.TryGetValueAsDreamObject(out DreamObject dreamObject)) { objectDefinition = dreamObject.ObjectDefinition; } else if (owner.TryGetValueAsPath(out DreamPath path)) { - objectDefinition = state.DreamManager.ObjectTree.GetObjectDefinition(path); + objectDefinition = state.Proc.ObjectTree.GetObjectDefinition(path); } else { throw new Exception("Invalid owner for initial() call " + owner); } @@ -418,7 +413,7 @@ private static void HandleSuffixPronoun(ref StringBuilder formattedString, ReadO DreamList list = listObject as DreamList; if (list == null) { - if (listObject.IsSubtypeOf(DreamPath.Atom) || listObject.IsSubtypeOf(DreamPath.World)) { + if (listObject.IsSubtypeOf(state.Proc.ObjectTree.Atom) || listObject.IsSubtypeOf(state.Proc.ObjectTree.World)) { list = listObject.GetVariable("contents").GetValueAsDreamList(); } else { throw new Exception("Value " + listValue + " is not a " + DreamPath.List + ", " + DreamPath.Atom + " or " + DreamPath.World); @@ -533,7 +528,7 @@ private static void HandleSuffixPronoun(ref StringBuilder formattedString, ReadO public static ProcStatus? PushType(DMProcState state) { int typeId = state.ReadInt(); - DreamPath path = state.DreamManager.ObjectTree.Types[typeId].Path; + DreamPath path = state.Proc.ObjectTree.Types[typeId].Path; state.Push(new DreamValue(path)); return null; @@ -726,19 +721,16 @@ private static void HandleSuffixPronoun(ref StringBuilder formattedString, ReadO return null; } - public static ProcStatus? BitNot(DMProcState state) - { + public static ProcStatus? BitNot(DMProcState state) { var input = state.Pop(); - if (input.TryGetValueAsInteger(out var value)) - { + if (input.TryGetValueAsInteger(out var value)) { state.Push(new DreamValue((~value) & 0xFFFFFF)); - } - else - { - if (input.TryGetValueAsDreamObjectOfType(DreamPath.Matrix, out _)) // TODO ~ on /matrix + } else { + if (input.TryGetValueAsDreamObjectOfType(state.Proc.ObjectTree.Matrix, out _)) // TODO ~ on /matrix { throw new NotImplementedException("/matrix does not support the '~' operator yet"); } + state.Push(new DreamValue(16777215)); // 2^24 - 1 } @@ -1219,7 +1211,9 @@ private static void HandleSuffixPronoun(ref StringBuilder formattedString, ReadO } if (value.TryGetValueAsDreamObject(out DreamObject dreamObject) && dreamObject != null) { - state.Push(new DreamValue(dreamObject.IsSubtypeOf(type) ? 1 : 0)); + var ancestor = state.Proc.ObjectTree.GetTreeEntry(type); + + state.Push(new DreamValue(dreamObject.IsSubtypeOf(ancestor) ? 1 : 0)); } else { state.Push(new DreamValue(0)); } @@ -1264,7 +1258,7 @@ private static void HandleSuffixPronoun(ref StringBuilder formattedString, ReadO } case DMReference.Type.GlobalProc: { instance = null; - proc = state.DreamManager.ObjectTree.Procs[procRef.Index]; + proc = state.Proc.ObjectTree.Procs[procRef.Index]; break; } @@ -1321,7 +1315,7 @@ private static void HandleSuffixPronoun(ref StringBuilder formattedString, ReadO if (fullProcPath.Elements.Length != 2 || fullProcPath.LastElement is null) //Only global procs are supported here currently throw new Exception($"Invalid call() proc \"{fullProcPath}\""); string procName = fullProcPath.LastElement; - if (!state.DreamManager.ObjectTree.TryGetGlobalProc(procName, out DreamProc? proc)) + if (!state.Proc.ObjectTree.TryGetGlobalProc(procName, out DreamProc? proc)) throw new Exception($"Failed to get global proc \"{procName}\""); state.Call(proc, state.Instance, arguments); @@ -1435,7 +1429,7 @@ private static void HandleSuffixPronoun(ref StringBuilder formattedString, ReadO public static ProcStatus? Throw(DMProcState state) { DreamValue value = state.Pop(); - if (value.TryGetValueAsDreamObjectOfType(DreamPath.Exception, out DreamObject exception)) { + if (value.TryGetValueAsDreamObjectOfType(state.Proc.ObjectTree.Exception, out DreamObject exception)) { throw new CancellingRuntime($"'throw' thrown ({exception.GetVariable("name").GetValueAsString()})"); } @@ -1544,7 +1538,7 @@ private static void PerformOutput(DreamValue a, DreamValue b) { if (leftRef.RefType == DMReference.Type.ListIndex) { (DreamValue indexing, _) = state.GetIndexReferenceValues(leftRef, peek: true); - if (indexing.TryGetValueAsDreamObjectOfType(DreamPath.Savefile, out var savefile)) { + if (indexing.TryGetValueAsDreamObjectOfType(state.Proc.ObjectTree.Savefile, out var savefile)) { // Savefiles get some special treatment. // "savefile[A] << B" is the same as "savefile[A] = B" @@ -1572,7 +1566,7 @@ private static void PerformOutput(DreamValue a, DreamValue b) { if (leftRef.RefType == DMReference.Type.ListIndex) { (DreamValue indexing, _) = state.GetIndexReferenceValues(leftRef, peek: true); - if (indexing.TryGetValueAsDreamObjectOfType(DreamPath.Savefile, out var savefile)) { + if (indexing.TryGetValueAsDreamObjectOfType(state.Proc.ObjectTree.Savefile, out var savefile)) { // Savefiles get some special treatment. // "savefile[A] >> B" is the same as "B = savefile[A]" @@ -1594,9 +1588,9 @@ private static void PerformOutput(DreamValue a, DreamValue b) { DreamObject receiver = state.Pop().GetValueAsDreamObject(); IEnumerable clients; - if (receiver.IsSubtypeOf(DreamPath.Mob)) { + if (receiver.IsSubtypeOf(state.Proc.ObjectTree.Mob)) { clients = new[] { state.DreamManager.GetConnectionFromMob(receiver) }; - } else if (receiver.IsSubtypeOf(DreamPath.Client)) { + } else if (receiver.IsSubtypeOf(state.Proc.ObjectTree.Client)) { clients = new[] { state.DreamManager.GetConnectionFromClient(receiver) }; } else if (receiver == state.DreamManager.WorldInstance) { clients = state.DreamManager.Connections; @@ -1625,7 +1619,7 @@ private static void PerformOutput(DreamValue a, DreamValue b) { var value = state.Pop(); DreamResource file; if (!value.TryGetValueAsDreamResource(out file)) { - if (value.TryGetValueAsDreamObjectOfType(DreamPath.Icon, out var icon)) { + if (value.TryGetValueAsDreamObjectOfType(state.Proc.ObjectTree.Icon, out var icon)) { (file, _) = DreamMetaObjectIcon.ObjectToDreamIcon[icon].GenerateDMI(); } else { throw new NotImplementedException(); @@ -1635,9 +1629,9 @@ private static void PerformOutput(DreamValue a, DreamValue b) { DreamObject receiver = state.Pop().GetValueAsDreamObject(); DreamObject client; - if (receiver.IsSubtypeOf(DreamPath.Mob)) { + if (receiver.IsSubtypeOf(state.Proc.ObjectTree.Mob)) { client = receiver.GetVariable("client").GetValueAsDreamObject(); - } else if (receiver.IsSubtypeOf(DreamPath.Client)) { + } else if (receiver.IsSubtypeOf(state.Proc.ObjectTree.Client)) { client = receiver; } else { throw new Exception("Invalid browse_rsc() recipient"); @@ -1674,9 +1668,9 @@ private static void PerformOutput(DreamValue a, DreamValue b) { } DreamObject? client; - if (receiver.IsSubtypeOf(DreamPath.Mob)) { - receiver.GetVariable("client").TryGetValueAsDreamObjectOfType(DreamPath.Client, out client); - } else if (receiver.IsSubtypeOf(DreamPath.Client)) { + if (receiver.IsSubtypeOf(state.Proc.ObjectTree.Mob)) { + receiver.GetVariable("client").TryGetValueAsDreamObjectOfType(state.Proc.ObjectTree.Client, out client); + } else if (receiver.IsSubtypeOf(state.Proc.ObjectTree.Client)) { client = receiver; } else { throw new Exception("Invalid output() recipient"); @@ -1700,7 +1694,7 @@ private static void PerformOutput(DreamValue a, DreamValue b) { DreamValue message, title, defaultValue; DreamValue firstArg = state.Pop(); - if (firstArg.TryGetValueAsDreamObjectOfType(DreamPath.Mob, out var recipientMob)) { + if (firstArg.TryGetValueAsDreamObjectOfType(state.Proc.ObjectTree.Mob, out var recipientMob)) { message = state.Pop(); title = state.Pop(); defaultValue = state.Pop(); @@ -1717,7 +1711,7 @@ private static void PerformOutput(DreamValue a, DreamValue b) { return null; } - if (recipientMob.GetVariable("client").TryGetValueAsDreamObjectOfType(DreamPath.Client, out var clientObject)) { + if (recipientMob.GetVariable("client").TryGetValueAsDreamObjectOfType(state.Proc.ObjectTree.Client, out var clientObject)) { DreamConnection? connection = state.DreamManager.GetConnectionFromClient(clientObject); if (connection == null) { state.Push(DreamValue.Null); @@ -1770,7 +1764,7 @@ private static void PerformOutput(DreamValue a, DreamValue b) { DreamValue value = state.Pop(); DreamList containerList; - if (container != null && container.IsSubtypeOf(DreamPath.Atom)) { + if (container != null && container.IsSubtypeOf(state.Proc.ObjectTree.Atom)) { container.GetVariable("contents").TryGetValueAsDreamList(out containerList); } else { containerList = container as DreamList; @@ -1785,10 +1779,11 @@ private static void PerformOutput(DreamValue a, DreamValue b) { return null; } + var ancestor = state.Proc.ObjectTree.GetTreeEntry(type); foreach (DreamValue containerItem in containerList.GetValues()) { if (!containerItem.TryGetValueAsDreamObject(out DreamObject dmObject)) continue; - if (dmObject.IsSubtypeOf(type)) { + if (dmObject.IsSubtypeOf(ancestor)) { state.Push(containerItem); return null; @@ -1925,7 +1920,7 @@ private static void PerformOutput(DreamValue a, DreamValue b) { if (owner.TryGetValueAsDreamObject(out DreamObject dreamObject)) { objectDefinition = dreamObject.ObjectDefinition; } else if (owner.TryGetValueAsPath(out DreamPath path)) { - objectDefinition = state.DreamManager.ObjectTree.GetObjectDefinition(path); + objectDefinition = state.Proc.ObjectTree.GetObjectDefinition(path); } else { throw new Exception("Invalid owner for issaved() call " + owner); } diff --git a/OpenDreamRuntime/Procs/DMProc.cs b/OpenDreamRuntime/Procs/DMProc.cs index 500f0e5435..9c5582fdff 100644 --- a/OpenDreamRuntime/Procs/DMProc.cs +++ b/OpenDreamRuntime/Procs/DMProc.cs @@ -8,6 +8,7 @@ namespace OpenDreamRuntime.Procs { sealed class DMProc : DreamProc { + public readonly IDreamObjectTree ObjectTree; public byte[] Bytecode { get; } private readonly int _maxStackSize; @@ -15,15 +16,14 @@ sealed class DMProc : DreamProc { public string? Source { get; set; } public int Line { get; set; } - public DMProc(DreamPath owningType, string name, DreamProc superProc, List argumentNames, List argumentTypes, byte[] bytecode, int maxStackSize, ProcAttributes attributes, string? verbName, string? verbCategory, string? verbDesc, sbyte? invisibility) - : base(owningType, name, superProc, attributes, argumentNames, argumentTypes, verbName, verbCategory, verbDesc, invisibility) - { + public DMProc(DreamPath owningType, string name, DreamProc superProc, List argumentNames, List argumentTypes, byte[] bytecode, int maxStackSize, ProcAttributes attributes, string? verbName, string? verbCategory, string? verbDesc, sbyte? invisibility, IDreamObjectTree objectTree) + : base(owningType, name, superProc, attributes, argumentNames, argumentTypes, verbName, verbCategory, verbDesc, invisibility) { + ObjectTree = objectTree; Bytecode = bytecode; _maxStackSize = maxStackSize; } - public override DMProcState CreateState(DreamThread thread, DreamObject? src, DreamObject? usr, DreamProcArguments arguments) - { + public override DMProcState CreateState(DreamThread thread, DreamObject? src, DreamObject? usr, DreamProcArguments arguments) { return new DMProcState(this, thread, _maxStackSize, src, usr, arguments); } } @@ -149,8 +149,8 @@ sealed class DMProcState : ProcState public readonly int ArgumentCount; public string? CurrentSource; public int CurrentLine; - private Stack>? _enumeratorStack; - public Stack> EnumeratorStack => _enumeratorStack ??= new Stack>(1); + private Stack? _enumeratorStack; + public Stack EnumeratorStack => _enumeratorStack ??= new(1); private int _pc = 0; @@ -158,7 +158,7 @@ sealed class DMProcState : ProcState private readonly DreamValue[] _localVariables; private readonly DMProc _proc; - public override DreamProc Proc => _proc; + public override DMProc Proc => _proc; public override (string?, int?) SourceLine => (CurrentSource, CurrentLine); @@ -354,7 +354,7 @@ public float ReadFloat() { public string ReadString() { int stringID = ReadInt(); - return DreamManager.ObjectTree.Strings[stringID]; + return Proc.ObjectTree.Strings[stringID]; } public DMReference ReadReference() { diff --git a/OpenDreamRuntime/Procs/DreamEnumerators.cs b/OpenDreamRuntime/Procs/DreamEnumerators.cs index 49b0d83afa..54838356e1 100644 --- a/OpenDreamRuntime/Procs/DreamEnumerators.cs +++ b/OpenDreamRuntime/Procs/DreamEnumerators.cs @@ -1,106 +1,133 @@ using OpenDreamRuntime.Objects; -using OpenDreamShared.Dream; namespace OpenDreamRuntime.Procs { - sealed class DreamProcRangeEnumerator : IEnumerator { + internal interface IDreamValueEnumerator { + public DreamValue Current { get; } + + public bool MoveNext(); + } + + /// + /// Enumerates a range of numbers with a given step + /// for (var/i in 1 to 10 step 2) + /// + sealed class DreamValueRangeEnumerator : IDreamValueEnumerator { + public DreamValue Current => new DreamValue(_current); + private float _current; - private readonly float _start; private readonly float _end; private readonly float _step; - public DreamProcRangeEnumerator(float rangeStart, float rangeEnd, float step) { + public DreamValueRangeEnumerator(float rangeStart, float rangeEnd, float step) { _current = rangeStart - step; - _start = rangeStart; _end = rangeEnd; _step = step; } - DreamValue IEnumerator.Current => new DreamValue(_current); - - public object Current => new DreamValue(_current); - public bool MoveNext() { _current += _step; return (_step > 0) ? _current <= _end : _current >= _end; } - - public void Reset() { - _current = _start - _step; - } - - public void Dispose() { - - } } - sealed class DreamObjectEnumerator : IEnumerator { + /// + /// Enumerates over an IEnumerable of DreamObjects, possibly filtering for a certain type + /// + sealed class DreamObjectEnumerator : IDreamValueEnumerator { + public DreamValue Current => new DreamValue(_dreamObjectEnumerator.Current); + private readonly IEnumerator _dreamObjectEnumerator; - private readonly DreamPath? _filterType; + private readonly IDreamObjectTree.TreeEntry? _filterType; - public DreamObjectEnumerator(IEnumerable dreamObjects, DreamPath? filterType = null) { + public DreamObjectEnumerator(IEnumerable dreamObjects, IDreamObjectTree.TreeEntry? filterType = null) { _dreamObjectEnumerator = dreamObjects.GetEnumerator(); _filterType = filterType; } - DreamValue IEnumerator.Current => new DreamValue(_dreamObjectEnumerator.Current); - - public object Current => new DreamValue(_dreamObjectEnumerator.Current); - public bool MoveNext() { bool hasNext = _dreamObjectEnumerator.MoveNext(); if (_filterType != null) { - while (hasNext && !_dreamObjectEnumerator.Current.IsSubtypeOf(_filterType.Value)) { + while (hasNext && !_dreamObjectEnumerator.Current.IsSubtypeOf(_filterType)) { hasNext = _dreamObjectEnumerator.MoveNext(); } } return hasNext; } + } - public void Reset() { - _dreamObjectEnumerator.Reset(); - } + /// + /// Enumerates over an array of DreamValues + /// for (var/i in list(1, 2, 3)) + /// + sealed class DreamValueArrayEnumerator : IDreamValueEnumerator { + private readonly DreamValue[]? _dreamValueArray; + private int _current = -1; - public void Dispose() { - _dreamObjectEnumerator.Dispose(); + public DreamValueArrayEnumerator(DreamValue[]? dreamValueArray) { + _dreamValueArray = dreamValueArray; } - } - - sealed class DreamValueAsObjectEnumerator : IEnumerator { - private readonly IEnumerator _dreamValueEnumerator; - private readonly DreamPath? _filterType; - public DreamValueAsObjectEnumerator(IEnumerable dreamValues, DreamPath? filterType = null) { - _dreamValueEnumerator = dreamValues.GetEnumerator(); - _filterType = filterType; + public DreamValue Current { + get { + if (_dreamValueArray == null) + return DreamValue.Null; + if (_current < _dreamValueArray.Length) + return _dreamValueArray[_current]; + return DreamValue.Null; + } } - DreamValue IEnumerator.Current => _dreamValueEnumerator.Current; + public bool MoveNext() { + if (_dreamValueArray == null) + return false; - public object Current => _dreamValueEnumerator.Current; + _current++; + if (_current >= _dreamValueArray.Length) + return false; - public bool MoveNext() { - bool hasNext = _dreamValueEnumerator.MoveNext(); - if (_filterType != null) { - while (hasNext && !_dreamValueEnumerator.Current.TryGetValueAsDreamObjectOfType(_filterType.Value, out _)) { - hasNext = _dreamValueEnumerator.MoveNext(); - } - } else { - while (hasNext && (_dreamValueEnumerator.Current.Type != DreamValue.DreamValueType.DreamObject || _dreamValueEnumerator.Current == DreamValue.Null)) { - hasNext = _dreamValueEnumerator.MoveNext(); - } - } + return true; + } + } - return hasNext; + /// + /// Enumerates over an array of DreamValues, filtering for a certain type + /// for (var/obj/item/I in contents) + /// + sealed class FilteredDreamValueArrayEnumerator : IDreamValueEnumerator { + private readonly DreamValue[]? _dreamValueArray; + private readonly IDreamObjectTree.TreeEntry _filterType; + private int _current = -1; + + public FilteredDreamValueArrayEnumerator(DreamValue[]? dreamValueArray, IDreamObjectTree.TreeEntry filterType) { + _dreamValueArray = dreamValueArray; + _filterType = filterType; } - public void Reset() { - _dreamValueEnumerator.Reset(); + public DreamValue Current { + get { + if (_dreamValueArray == null) + return DreamValue.Null; + if (_current < _dreamValueArray.Length) + return _dreamValueArray[_current]; + return DreamValue.Null; + } } - public void Dispose() { - _dreamValueEnumerator.Dispose(); + public bool MoveNext() { + if (_dreamValueArray == null) + return false; + + do { + _current++; + if (_current >= _dreamValueArray.Length) + return false; + + DreamValue value = _dreamValueArray[_current]; + if (value.TryGetValueAsDreamObjectOfType(_filterType, out _)) + return true; + } while (true); } } } diff --git a/OpenDreamRuntime/Procs/InitDreamObject.cs b/OpenDreamRuntime/Procs/InitDreamObject.cs index 404aeb5491..c5b1b14064 100644 --- a/OpenDreamRuntime/Procs/InitDreamObject.cs +++ b/OpenDreamRuntime/Procs/InitDreamObject.cs @@ -5,6 +5,8 @@ namespace OpenDreamRuntime.Procs { sealed class InitDreamObjectState : ProcState { [Dependency] private readonly IDreamManager _dreamMan = default!; + [Dependency] private readonly IDreamObjectTree _objectTree = default!; + enum Stage { // Need to call the object's (init) proc Init, @@ -50,7 +52,7 @@ protected override ProcStatus InternalResume() goto switch_start; } - var proc = _dreamMan.ObjectTree.Procs[src.ObjectDefinition.InitializationProc.Value]; + var proc = _objectTree.Procs[src.ObjectDefinition.InitializationProc.Value]; var initProcState = proc.CreateState(Thread, src, _usr, new(null)); Thread.PushProcState(initProcState); return ProcStatus.Called; diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs index 960920237c..d31cf423c7 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs @@ -3,8 +3,9 @@ namespace OpenDreamRuntime.Procs.Native { static class DreamProcNative { - public static void SetupNativeProcs(DreamObjectTree objectTree) { + public static void SetupNativeProcs(IDreamObjectTree objectTree) { DreamProcNativeRoot.DreamManager = IoCManager.Resolve(); + DreamProcNativeRoot.ObjectTree = objectTree; objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_abs); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_alert); @@ -112,32 +113,32 @@ public static void SetupNativeProcs(DreamObjectTree objectTree) { objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_winset); DreamObjectDefinition list = objectTree.GetObjectDefinition(DreamPath.List); - list.SetNativeProc(DreamProcNativeList.NativeProc_Add); - list.SetNativeProc(DreamProcNativeList.NativeProc_Copy); - list.SetNativeProc(DreamProcNativeList.NativeProc_Cut); - list.SetNativeProc(DreamProcNativeList.NativeProc_Find); - list.SetNativeProc(DreamProcNativeList.NativeProc_Insert); - list.SetNativeProc(DreamProcNativeList.NativeProc_Remove); - list.SetNativeProc(DreamProcNativeList.NativeProc_Swap); + objectTree.SetNativeProc(list, DreamProcNativeList.NativeProc_Add); + objectTree.SetNativeProc(list, DreamProcNativeList.NativeProc_Copy); + objectTree.SetNativeProc(list, DreamProcNativeList.NativeProc_Cut); + objectTree.SetNativeProc(list, DreamProcNativeList.NativeProc_Find); + objectTree.SetNativeProc(list, DreamProcNativeList.NativeProc_Insert); + objectTree.SetNativeProc(list, DreamProcNativeList.NativeProc_Remove); + objectTree.SetNativeProc(list, DreamProcNativeList.NativeProc_Swap); DreamObjectDefinition regex = objectTree.GetObjectDefinition(DreamPath.Regex); - regex.SetNativeProc(DreamProcNativeRegex.NativeProc_Find); - regex.SetNativeProc(DreamProcNativeRegex.NativeProc_Replace); + objectTree.SetNativeProc(regex, DreamProcNativeRegex.NativeProc_Find); + objectTree.SetNativeProc(regex, DreamProcNativeRegex.NativeProc_Replace); DreamObjectDefinition icon = objectTree.GetObjectDefinition(DreamPath.Icon); - icon.SetNativeProc(DreamProcNativeIcon.NativeProc_Width); - icon.SetNativeProc(DreamProcNativeIcon.NativeProc_Height); - icon.SetNativeProc(DreamProcNativeIcon.NativeProc_Insert); - icon.SetNativeProc(DreamProcNativeIcon.NativeProc_Blend); - icon.SetNativeProc(DreamProcNativeIcon.NativeProc_Scale); + objectTree.SetNativeProc(icon, DreamProcNativeIcon.NativeProc_Width); + objectTree.SetNativeProc(icon, DreamProcNativeIcon.NativeProc_Height); + objectTree.SetNativeProc(icon, DreamProcNativeIcon.NativeProc_Insert); + objectTree.SetNativeProc(icon, DreamProcNativeIcon.NativeProc_Blend); + objectTree.SetNativeProc(icon, DreamProcNativeIcon.NativeProc_Scale); //DreamObjectDefinition savefile = objectTree.GetObjectDefinitionFromPath(DreamPath.Savefile); //savefile.SetNativeProc(DreamProcNativeSavefile.NativeProc_Flush); DreamObjectDefinition world = objectTree.GetObjectDefinition(DreamPath.World); - world.SetNativeProc(DreamProcNativeWorld.NativeProc_Export); - world.SetNativeProc(DreamProcNativeWorld.NativeProc_GetConfig); - world.SetNativeProc(DreamProcNativeWorld.NativeProc_SetConfig); + objectTree.SetNativeProc(world, DreamProcNativeWorld.NativeProc_Export); + objectTree.SetNativeProc(world, DreamProcNativeWorld.NativeProc_GetConfig); + objectTree.SetNativeProc(world, DreamProcNativeWorld.NativeProc_SetConfig); } } } diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs index f0695269a0..d6dbe8d430 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs @@ -37,8 +37,9 @@ public static DreamValue NativeProc_Insert(DreamObject instance, DreamObject usr // TODO: moving & delay + var objectTree = IoCManager.Resolve(); var resourceManager = IoCManager.Resolve(); - var (iconRsc, iconDescription) = DreamMetaObjectIcon.GetIconResourceAndDescription(resourceManager, newIcon); + var (iconRsc, iconDescription) = DreamMetaObjectIcon.GetIconResourceAndDescription(objectTree, resourceManager, newIcon); DreamIcon iconObj = DreamMetaObjectIcon.ObjectToDreamIcon[instance]; iconObj.InsertStates(iconRsc, iconDescription, iconState, dir, frame); // TODO: moving & delay diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRegex.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRegex.cs index 8df34195d3..be452c11ed 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRegex.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRegex.cs @@ -77,8 +77,8 @@ public static async Task RegexReplace(AsyncNativeProc.State state, D } if (replace.TryGetValueAsPath(out var procPath) && procPath.LastElement is not null) { - var dreamMan = IoCManager.Resolve(); - if (dreamMan.ObjectTree.TryGetGlobalProc(procPath.LastElement, out DreamProc? proc)) { + var objectTree = IoCManager.Resolve(); + if (objectTree.TryGetGlobalProc(procPath.LastElement, out DreamProc? proc)) { return await DoProcReplace(state, proc); } } diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs index 3868090256..e527792262 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs @@ -23,6 +23,7 @@ namespace OpenDreamRuntime.Procs.Native { static class DreamProcNativeRoot { // I don't want to edit 100 procs to have the DreamManager passed to them public static IDreamManager DreamManager; + public static IDreamObjectTree ObjectTree; [DreamProc("abs")] [DreamProcParameter("A", Type = DreamValueType.Float)] @@ -43,7 +44,7 @@ public static async Task NativeProc_alert(AsyncNativeProc.State stat string message, title, button1, button2, button3; DreamValue usrArgument = state.Arguments.GetArgument(0, "Usr"); - if (usrArgument.TryGetValueAsDreamObjectOfType(DreamPath.Mob, out var mob)) { + if (usrArgument.TryGetValueAsDreamObjectOfType(ObjectTree.Mob, out var mob)) { message = state.Arguments.GetArgument(1, "Message").Stringify(); title = state.Arguments.GetArgument(2, "Title").Stringify(); button1 = state.Arguments.GetArgument(3, "Button1").Stringify(); @@ -72,7 +73,7 @@ public static async Task NativeProc_alert(AsyncNativeProc.State stat [DreamProcParameter("flags", Type = DreamValueType.Float)] public static DreamValue NativeProc_animate(DreamObject instance, DreamObject usr, DreamProcArguments arguments) { // TODO: Leaving out the Object var adds a new step to the previous animation - if (!arguments.GetArgument(0, "Object").TryGetValueAsDreamObjectOfType(DreamPath.Atom, out var obj)) + if (!arguments.GetArgument(0, "Object").TryGetValueAsDreamObjectOfType(ObjectTree.Atom, out var obj)) return DreamValue.Null; // TODO: Is this the correct behavior for invalid time? if (!arguments.GetArgument(0, "time").TryGetValueAsFloat(out float time)) @@ -321,7 +322,7 @@ public static DreamValue NativeProc_fcopy(DreamObject instance, DreamObject usr, string? src; if (arg1.TryGetValueAsDreamResource(out DreamResource arg1Rsc)) { src = arg1Rsc.ResourcePath; - } else if (arg1.TryGetValueAsDreamObjectOfType(DreamPath.Savefile, out var savefile)) { + } else if (arg1.TryGetValueAsDreamObjectOfType(ObjectTree.Savefile, out var savefile)) { src = DreamMetaObjectSavefile.ObjectToSavefile[savefile].Resource.ResourcePath; } else if (!arg1.TryGetValueAsString(out src)) { throw new Exception($"Bad src file {arg1}"); @@ -467,7 +468,7 @@ public static DreamValue NativeProc_filter(DreamObject instance, DreamObject usr if (filter == null) throw new Exception($"Failed to create filter of type {filterType}"); - DreamObject filterObject = DreamManager.ObjectTree.CreateObject(DreamPath.Filter); + DreamObject filterObject = ObjectTree.CreateObject(DreamPath.Filter); DreamMetaObjectFilter.DreamObjectToFilter[filterObject] = filter; return new DreamValue(filterObject); } @@ -735,7 +736,7 @@ public static DreamValue NativeProc_icon_states(DreamObject instance, DreamObjec var dmiDescription = DMIParser.ParseDMI(new MemoryStream(resource.ResourceData)); return new DreamValue(DreamList.Create(dmiDescription.States.Keys.ToArray())); - } else if (arg.TryGetValueAsDreamObjectOfType(DreamPath.Icon, out var icon)) { + } else if (arg.TryGetValueAsDreamObjectOfType(ObjectTree.Icon, out var icon)) { return new DreamValue(DreamList.Create(DreamMetaObjectIcon.ObjectToDreamIcon[icon].States.Keys.ToArray())); } else { throw new Exception($"Bad icon {arg}"); @@ -749,7 +750,7 @@ public static DreamValue NativeProc_icon_states(DreamObject instance, DreamObjec [DreamProcParameter("layer", Type = DreamValueType.Float)] [DreamProcParameter("dir", Type = DreamValueType.Float)] public static DreamValue NativeProc_image(DreamObject instance, DreamObject usr, DreamProcArguments arguments) { - DreamObject imageObject = DreamManager.ObjectTree.CreateObject(DreamPath.Image); + DreamObject imageObject = ObjectTree.CreateObject(DreamPath.Image); imageObject.InitSpawn(arguments); return new DreamValue(imageObject); } @@ -760,7 +761,7 @@ public static DreamValue NativeProc_isarea(DreamObject instance, DreamObject usr List locs = arguments.GetAllArguments(); foreach (DreamValue loc in locs) { - if (!loc.TryGetValueAsDreamObjectOfType(DreamPath.Area, out _)) return new DreamValue(0); + if (!loc.TryGetValueAsDreamObjectOfType(ObjectTree.Area, out _)) return new DreamValue(0); } return new DreamValue(1); @@ -778,7 +779,7 @@ public static DreamValue NativeProc_isfile(DreamObject instance, DreamObject usr [DreamProcParameter("Icon")] public static DreamValue NativeProc_isicon(DreamObject instance, DreamObject usr, DreamProcArguments arguments) { DreamValue icon = arguments.GetArgument(0, "Icon"); - if (icon.TryGetValueAsDreamObjectOfType(DreamPath.Icon, out _)) + if (icon.TryGetValueAsDreamObjectOfType(ObjectTree.Icon, out _)) return new DreamValue(1); else if (icon.TryGetValueAsDreamResource(out DreamResource resource)) { switch (Path.GetExtension(resource.ResourcePath)) { @@ -819,7 +820,7 @@ public static DreamValue NativeProc_isloc(DreamObject instance, DreamObject usr, foreach (DreamValue loc in locs) { if (loc.TryGetValueAsDreamObject(out DreamObject? locObject) && locObject is not null) { - bool isLoc = locObject.IsSubtypeOf(DreamPath.Mob) || locObject.IsSubtypeOf(DreamPath.Obj) || locObject.IsSubtypeOf(DreamPath.Turf) || locObject.IsSubtypeOf(DreamPath.Area); + bool isLoc = locObject.IsSubtypeOf(ObjectTree.Mob) || locObject.IsSubtypeOf(ObjectTree.Obj) || locObject.IsSubtypeOf(ObjectTree.Turf) || locObject.IsSubtypeOf(ObjectTree.Area); if (!isLoc) { return new DreamValue(0); @@ -838,7 +839,7 @@ public static DreamValue NativeProc_ismob(DreamObject instance, DreamObject usr, List locs = arguments.GetAllArguments(); foreach (DreamValue loc in locs) { - if (!loc.TryGetValueAsDreamObjectOfType(DreamPath.Mob, out _)) + if (!loc.TryGetValueAsDreamObjectOfType(ObjectTree.Mob, out _)) return new DreamValue(0); } @@ -851,7 +852,7 @@ public static DreamValue NativeProc_ismovable(DreamObject instance, DreamObject List locs = arguments.GetAllArguments(); foreach (DreamValue loc in locs) { - if (!loc.TryGetValueAsDreamObjectOfType(DreamPath.Movable, out _)) { + if (!loc.TryGetValueAsDreamObjectOfType(ObjectTree.Movable, out _)) { return new DreamValue(0); } } @@ -893,9 +894,9 @@ public static DreamValue NativeProc_ispath(DreamObject instance, DreamObject usr if (value.TryGetValueAsPath(out DreamPath valuePath)) { if (type.TryGetValueAsPath(out DreamPath typePath)) { - DreamObjectDefinition valueDefinition = DreamManager.ObjectTree.GetObjectDefinition(valuePath); + DreamObjectDefinition valueDefinition = ObjectTree.GetObjectDefinition(valuePath); - return new DreamValue(valueDefinition.IsSubtypeOf(typePath) ? 1 : 0); + return new DreamValue(valueDefinition.IsSubtypeOf(ObjectTree.GetTreeEntry(typePath)) ? 1 : 0); } else { return new DreamValue(1); } @@ -918,7 +919,7 @@ public static DreamValue NativeProc_isturf(DreamObject instance, DreamObject usr List locs = arguments.GetAllArguments(); foreach (DreamValue loc in locs) { - if (!loc.TryGetValueAsDreamObjectOfType(DreamPath.Turf, out _)) { + if (!loc.TryGetValueAsDreamObjectOfType(ObjectTree.Turf, out _)) { return new DreamValue(0); } } @@ -1018,7 +1019,7 @@ private static DreamValue CreateValueFromJsonElement(JsonElement jsonElement) } if (value.Type == DreamValueType.DreamObject) { if (value.Value == null) return null; - if(value.TryGetValueAsDreamObjectOfType(DreamPath.Matrix,out var matrix)) { // Special behaviour for /matrix values + if(value.TryGetValueAsDreamObjectOfType(ObjectTree.Matrix, out var matrix)) { // Special behaviour for /matrix values StringBuilder builder = new(13); // 13 is the minimum character count this could have: "[1,2,3,4,5,6]" builder.Append('['); builder.AppendJoin(',',DreamMetaObjectMatrix.EnumerateMatrix(matrix)); @@ -1479,7 +1480,7 @@ public static DreamValue NativeProc_regex(DreamObject instance, DreamObject usr, return new DreamValue(text.Replace("$", "$$")); }; } - var newRegex = DreamManager.ObjectTree.CreateObject(DreamPath.Regex); + var newRegex = ObjectTree.CreateObject(DreamPath.Regex); newRegex.InitSpawn(arguments); return new DreamValue(newRegex); } @@ -1497,7 +1498,7 @@ public static async Task NativeProc_replacetext(AsyncNativeProc.Stat int start = state.Arguments.GetArgument(3, "Start").GetValueAsInteger(); //1-indexed int end = state.Arguments.GetArgument(4, "End").GetValueAsInteger(); //1-indexed - if (needle.TryGetValueAsDreamObjectOfType(DreamPath.Regex, out var regexObject)) { + if (needle.TryGetValueAsDreamObjectOfType(ObjectTree.Regex, out var regexObject)) { // According to the docs, this is the same as /regex.Replace() return await DreamProcNativeRegex.RegexReplace(state, regexObject, haystack, replacementArg, start, end); } @@ -1853,7 +1854,7 @@ public static DreamValue NativeProc_sorttextEx(DreamObject instance, DreamObject [DreamProcParameter("channel", Type = DreamValueType.Float)] [DreamProcParameter("volume", Type = DreamValueType.Float)] public static DreamValue NativeProc_sound(DreamObject instance, DreamObject usr, DreamProcArguments arguments) { - DreamObject soundObject = DreamManager.ObjectTree.CreateObject(DreamPath.Sound); + DreamObject soundObject = ObjectTree.CreateObject(DreamPath.Sound); soundObject.InitSpawn(arguments); return new DreamValue(soundObject); } @@ -2015,7 +2016,7 @@ public static DreamValue NativeProc_text2path(DreamObject instance, DreamObject } DreamPath path = new DreamPath(text); - if (DreamManager.ObjectTree.HasTreeEntry(path)) { + if (ObjectTree.HasTreeEntry(path)) { return new DreamValue(path); } else { return DreamValue.Null; @@ -2091,12 +2092,12 @@ public static DreamValue NativeProc_typesof(DreamObject instance, DreamObject us var stringToPath = new DreamPath(typeString); if (stringToPath.LastElement == "proc") { DreamPath objectPath = stringToPath.AddToPath(".."); - if (!DreamManager.ObjectTree.HasTreeEntry(objectPath)) + if (!ObjectTree.HasTreeEntry(objectPath)) { continue; } } - else if (!DreamManager.ObjectTree.HasTreeEntry(stringToPath)) { + else if (!ObjectTree.HasTreeEntry(stringToPath)) { continue; } typePath = stringToPath; @@ -2110,13 +2111,13 @@ public static DreamValue NativeProc_typesof(DreamObject instance, DreamObject us if (typePath.LastElement == "proc") { DreamPath objectTypePath = typePath.AddToPath(".."); - DreamObjectDefinition objectDefinition = DreamManager.ObjectTree.GetObjectDefinition(objectTypePath); + DreamObjectDefinition objectDefinition = ObjectTree.GetObjectDefinition(objectTypePath); foreach (KeyValuePair proc in objectDefinition.Procs) { list.AddValue(new DreamValue(proc.Key)); } } else { - var descendants = DreamManager.ObjectTree.GetAllDescendants(typePath); + var descendants = ObjectTree.GetAllDescendants(typePath); foreach (var descendant in descendants) { list.AddValue(new DreamValue(descendant.ObjectDefinition.Type)); @@ -2287,9 +2288,9 @@ public static async Task NativeProc_winexists(AsyncNativeProc.State } DreamConnection connection; - if (player.TryGetValueAsDreamObjectOfType(DreamPath.Mob, out DreamObject mob)) { + if (player.TryGetValueAsDreamObjectOfType(ObjectTree.Mob, out DreamObject mob)) { connection = DreamManager.GetConnectionFromMob(mob); - } else if (player.TryGetValueAsDreamObjectOfType(DreamPath.Client, out DreamObject client)) { + } else if (player.TryGetValueAsDreamObjectOfType(ObjectTree.Client, out DreamObject client)) { connection = DreamManager.GetConnectionFromClient(client); } else { throw new Exception($"Invalid client {player}"); @@ -2309,9 +2310,9 @@ public static DreamValue NativeProc_winset(DreamObject instance, DreamObject usr string winsetParams = arguments.GetArgument(2, "params").GetValueAsString(); DreamConnection connection; - if (player.TryGetValueAsDreamObjectOfType(DreamPath.Mob, out var mob)) { + if (player.TryGetValueAsDreamObjectOfType(ObjectTree.Mob, out var mob)) { connection = DreamManager.GetConnectionFromMob(mob); - } else if (player.TryGetValueAsDreamObjectOfType(DreamPath.Client, out var client)) { + } else if (player.TryGetValueAsDreamObjectOfType(ObjectTree.Client, out var client)) { connection = DreamManager.GetConnectionFromClient(client); } else { throw new ArgumentException($"Invalid \"player\" argument {player}"); diff --git a/OpenDreamRuntime/ServerContentIoC.cs b/OpenDreamRuntime/ServerContentIoC.cs index 148ad89e7a..2252048079 100644 --- a/OpenDreamRuntime/ServerContentIoC.cs +++ b/OpenDreamRuntime/ServerContentIoC.cs @@ -1,4 +1,5 @@ -using OpenDreamRuntime.Procs; +using OpenDreamRuntime.Objects; +using OpenDreamRuntime.Procs; using OpenDreamRuntime.Procs.DebugAdapter; using OpenDreamRuntime.Resources; @@ -6,6 +7,7 @@ namespace OpenDreamRuntime { public static class ServerContentIoC { public static void Register(bool unitTests = false) { IoCManager.Register(); + IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); From 7640e989c64da61ba5325d1671a6cb6f213d1f79 Mon Sep 17 00:00:00 2001 From: wixoaGit Date: Wed, 14 Dec 2022 14:33:34 -0500 Subject: [PATCH 2/3] Implement color blending in `/icon.Blend()`, `ICON_ADD`, and `ICON_SUBTRACT` --- OpenDreamRuntime/DreamManager.cs | 2 +- OpenDreamRuntime/Objects/DreamIcon.cs | 106 +++++++++++++----- OpenDreamRuntime/Objects/DreamObjectTree.cs | 4 +- .../MetaObjects/DreamMetaObjectIcon.cs | 15 ++- OpenDreamRuntime/Procs/DMOpcodeHandlers.cs | 18 ++- OpenDreamRuntime/Procs/DMProc.cs | 5 +- .../Procs/Native/DreamProcNativeIcon.cs | 18 ++- 7 files changed, 128 insertions(+), 40 deletions(-) diff --git a/OpenDreamRuntime/DreamManager.cs b/OpenDreamRuntime/DreamManager.cs index 3618f5fd2c..bdb3847625 100644 --- a/OpenDreamRuntime/DreamManager.cs +++ b/OpenDreamRuntime/DreamManager.cs @@ -59,7 +59,7 @@ public void StartWorld() { // Call global with waitfor=FALSE if (_compiledJson.GlobalInitProc is ProcDefinitionJson initProcDef) { - var globalInitProc = new DMProc(DreamPath.Root, "(global init)", null, null, null, initProcDef.Bytecode, initProcDef.MaxStackSize, initProcDef.Attributes, initProcDef.VerbName, initProcDef.VerbCategory, initProcDef.VerbDesc, initProcDef.Invisibility, _objectTree); + var globalInitProc = new DMProc(DreamPath.Root, "(global init)", null, null, null, initProcDef.Bytecode, initProcDef.MaxStackSize, initProcDef.Attributes, initProcDef.VerbName, initProcDef.VerbCategory, initProcDef.VerbDesc, initProcDef.Invisibility, _objectTree, _dreamResourceManager); globalInitProc.Spawn(WorldInstance, new DreamProcArguments()); } diff --git a/OpenDreamRuntime/Objects/DreamIcon.cs b/OpenDreamRuntime/Objects/DreamIcon.cs index a2e09e8f27..a19747a7a7 100644 --- a/OpenDreamRuntime/Objects/DreamIcon.cs +++ b/OpenDreamRuntime/Objects/DreamIcon.cs @@ -7,6 +7,7 @@ using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using Color = Robust.Shared.Maths.Color; using ParsedDMIDescription = OpenDreamShared.Resources.DMIParser.ParsedDMIDescription; using ParsedDMIState = OpenDreamShared.Resources.DMIParser.ParsedDMIState; using ParsedDMIFrame = OpenDreamShared.Resources.DMIParser.ParsedDMIFrame; @@ -280,7 +281,8 @@ public interface IDreamIconOperation { public void ApplyToFrame(Rgba32[] pixels, int imageSpan, int frame, UIBox2i bounds); } -public sealed class DreamIconOperationBlend : IDreamIconOperation { +[Virtual] +public class DreamIconOperationBlend : IDreamIconOperation { // With the same values as the ICON_* defines in DMStandard public enum BlendType { Add = 0, @@ -294,24 +296,74 @@ public enum BlendType { private readonly BlendType _type; private readonly int _xOffset, _yOffset; - private readonly Image _blending; - private readonly ParsedDMIDescription _blendingDescription; - public DreamIconOperationBlend(BlendType type, DreamValue blending, int xOffset, int yOffset) { + protected DreamIconOperationBlend(BlendType type, int xOffset, int yOffset) { _type = type; _xOffset = xOffset; _yOffset = yOffset; + if (_type is not BlendType.Overlay and not BlendType.Underlay and not BlendType.Add and not BlendType.Subtract) + throw new NotImplementedException($"\"{_type}\" blending is not implemented"); + } + + public virtual void ApplyToFrame(Rgba32[] pixels, int imageSpan, int frame, UIBox2i bounds) { + throw new NotImplementedException(); + } + + protected void BlendPixel(Rgba32[] pixels, int dstPixelPosition, Rgba32 src) { + Rgba32 dst = pixels[dstPixelPosition]; + + switch (_type) { + case BlendType.Add: { + pixels[dstPixelPosition].R = (byte)Math.Min(dst.R + src.R, byte.MaxValue); + pixels[dstPixelPosition].G = (byte)Math.Min(dst.G + src.G, byte.MaxValue); + pixels[dstPixelPosition].B = (byte)Math.Min(dst.B + src.B, byte.MaxValue); + + // BYOND uses the smaller of the two alphas + pixels[dstPixelPosition].A = Math.Min(dst.A, src.A); + break; + } + case BlendType.Subtract: { + pixels[dstPixelPosition].R = (byte)Math.Max(dst.R - src.R, byte.MinValue); + pixels[dstPixelPosition].G = (byte)Math.Max(dst.G - src.G, byte.MinValue); + pixels[dstPixelPosition].B = (byte)Math.Max(dst.B - src.B, byte.MinValue); + + // BYOND uses the smaller of the two alphas + pixels[dstPixelPosition].A = Math.Min(dst.A, src.A); + break; + } + + case BlendType.Overlay: { + pixels[dstPixelPosition].R = (byte) (dst.R + (src.R - dst.R) * src.A / 255); + pixels[dstPixelPosition].G = (byte) (dst.G + (src.G - dst.G) * src.A / 255); + pixels[dstPixelPosition].B = (byte) (dst.B + (src.B - dst.B) * src.A / 255); + + byte highAlpha = Math.Max(dst.A, src.A); + byte lowAlpha = Math.Min(dst.A, src.A); + pixels[dstPixelPosition].A = (byte) (highAlpha + (highAlpha * lowAlpha / 255)); + break; + } + case BlendType.Underlay: { + // Opposite of overlay + (dst, src) = (src, dst); + goto case BlendType.Overlay; + } + } + } +} + +public sealed class DreamIconOperationBlendImage : DreamIconOperationBlend { + private readonly Image _blending; + private readonly ParsedDMIDescription _blendingDescription; + + public DreamIconOperationBlendImage(BlendType type, int xOffset, int yOffset, DreamValue blending) : base(type, xOffset, yOffset) { var objectTree = IoCManager.Resolve(); var resourceManager = IoCManager.Resolve(); (var blendingResource, _blendingDescription) = DreamMetaObjectIcon.GetIconResourceAndDescription(objectTree, resourceManager, blending); _blending = resourceManager.LoadImage(blendingResource); - - if (_type is not BlendType.Overlay and not BlendType.Underlay) - throw new NotImplementedException($"\"{_type}\" blending is not implemented"); } - public void ApplyToFrame(Rgba32[] pixels, int imageSpan, int frame, UIBox2i bounds) { + public override void ApplyToFrame(Rgba32[] pixels, int imageSpan, int frame, UIBox2i bounds) { _blending.ProcessPixelRows(accessor => { // The first frame of the source image blends with the first frame of the destination image // The second frame blends with the second, and so on @@ -330,23 +382,7 @@ public void ApplyToFrame(Rgba32[] pixels, int imageSpan, int frame, UIBox2i boun Rgba32 dst = pixels[dstPixelPosition]; Rgba32 src = row[srcFramePos.Value.X + x - bounds.Left]; - switch (_type) { - case BlendType.Overlay: { - pixels[dstPixelPosition].R = (byte) (dst.R + (src.R - dst.R) * src.A / 255); - pixels[dstPixelPosition].G = (byte) (dst.G + (src.G - dst.G) * src.A / 255); - pixels[dstPixelPosition].B = (byte) (dst.B + (src.B - dst.B) * src.A / 255); - - byte highAlpha = Math.Max(dst.A, src.A); - byte lowAlpha = Math.Min(dst.A, src.A); - pixels[dstPixelPosition].A = (byte) (highAlpha + (highAlpha * lowAlpha / 255)); - break; - } - case BlendType.Underlay: { - // Opposite of overlay - (dst, src) = (src, dst); - goto case BlendType.Overlay; - } - } + BlendPixel(pixels, dstPixelPosition, src); } } }); @@ -369,3 +405,23 @@ public void ApplyToFrame(Rgba32[] pixels, int imageSpan, int frame, UIBox2i boun return (column * _blendingDescription.Width, row * _blendingDescription.Height); } } + +public sealed class DreamIconOperationBlendColor : DreamIconOperationBlend { + private readonly Rgba32 _color; + + public DreamIconOperationBlendColor(BlendType type, int xOffset, int yOffset, Color color) : base(type, xOffset, yOffset) { + _color = new Rgba32(color.RByte, color.GByte, color.BByte, color.AByte); + } + + public override void ApplyToFrame(Rgba32[] pixels, int imageSpan, int frame, UIBox2i bounds) { + // TODO: x & y offsets + + for (int y = bounds.Top; y < bounds.Bottom; y++) { + for (int x = bounds.Left; x < bounds.Right; x++) { + int dstPixelPosition = (y * imageSpan) + x; + + BlendPixel(pixels, dstPixelPosition, _color); + } + } + } +} diff --git a/OpenDreamRuntime/Objects/DreamObjectTree.cs b/OpenDreamRuntime/Objects/DreamObjectTree.cs index 21b67e49a3..bccaf0c0d2 100644 --- a/OpenDreamRuntime/Objects/DreamObjectTree.cs +++ b/OpenDreamRuntime/Objects/DreamObjectTree.cs @@ -38,6 +38,8 @@ public sealed class DreamObjectTree : IDreamObjectTree { private Dictionary _pathToType = new(); private Dictionary _globalProcIds; + [Dependency] private readonly DreamResourceManager _resourceManager = default!; + public void LoadJson(DreamCompiledJson json) { Strings = json.Strings; @@ -296,7 +298,7 @@ public DreamProc LoadProcJson(DreamTypeJson[] types, ProcDefinitionJson procDefi } DreamPath owningType = new DreamPath(types[procDefinition.OwningTypeId].Path); - var proc = new DMProc(owningType, procDefinition.Name, null, argumentNames, argumentTypes, bytecode, procDefinition.MaxStackSize, procDefinition.Attributes, procDefinition.VerbName, procDefinition.VerbCategory, procDefinition.VerbDesc, procDefinition.Invisibility, this); + var proc = new DMProc(owningType, procDefinition.Name, null, argumentNames, argumentTypes, bytecode, procDefinition.MaxStackSize, procDefinition.Attributes, procDefinition.VerbName, procDefinition.VerbCategory, procDefinition.VerbDesc, procDefinition.Invisibility, this, _resourceManager); proc.Source = procDefinition.Source; proc.Line = procDefinition.Line; return proc; diff --git a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectIcon.cs b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectIcon.cs index 570a82f466..1221a3b691 100644 --- a/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectIcon.cs +++ b/OpenDreamRuntime/Objects/MetaObjects/DreamMetaObjectIcon.cs @@ -30,8 +30,7 @@ public void OnObjectCreated(DreamObject dreamObject, DreamProcArguments creation DreamValue frame = creationArguments.GetArgument(3, "frame"); DreamValue moving = creationArguments.GetArgument(4, "moving"); - DreamIcon dreamIcon = new(_rscMan); - ObjectToDreamIcon.Add(dreamObject, dreamIcon); + var dreamIcon = InitializeIcon(_rscMan, dreamObject); if (icon != DreamValue.Null) { // TODO: Could maybe have an alternative path for /icon values so the DMI doesn't have to be generated @@ -61,6 +60,18 @@ public void OnVariableSet(DreamObject dreamObject, string varName, DreamValue va } } + /// + /// A fast path for initializing an /icon object + /// + /// Doesn't call any DM code + /// The /icon's DreamIcon + public static DreamIcon InitializeIcon(DreamResourceManager rscMan, DreamObject icon) { + DreamIcon dreamIcon = new(rscMan); + + ObjectToDreamIcon.Add(icon, dreamIcon); + return dreamIcon; + } + public static (DreamResource Resource, ParsedDMIDescription Description) GetIconResourceAndDescription( IDreamObjectTree objectTree, DreamResourceManager resourceManager, DreamValue value) { if (value.TryGetValueAsDreamObjectOfType(objectTree.Icon, out var iconObj)) { diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index b5ff8ff493..914110e411 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.MetaObjects; +using OpenDreamRuntime.Procs.Native; using OpenDreamRuntime.Resources; using OpenDreamShared.Dream; using OpenDreamShared.Dream.Procs; @@ -616,11 +617,21 @@ private static void HandleSuffixPronoun(ref StringBuilder formattedString, ReadO return null; } else { - throw new Exception("Invalid append operation on " + first + " and " + second); + throw new Exception($"Invalid append operation on {first} and {second}"); } } else { result = second; } + } else if (first.TryGetValueAsDreamResource(out _) || first.TryGetValueAsDreamObjectOfType(state.Proc.ObjectTree.Icon, 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 + DreamObject iconObj = state.Proc.ObjectTree.CreateObject(DreamPath.Icon); + var icon = DreamMetaObjectIcon.InitializeIcon(state.Proc.ResourceManager, iconObj); + var from = DreamMetaObjectIcon.GetIconResourceAndDescription(state.Proc.ObjectTree, state.Proc.ResourceManager, first); + + icon.InsertStates(from.Resource, from.Description, DreamValue.Null, DreamValue.Null, DreamValue.Null); + DreamProcNativeIcon.Blend(icon, second, DreamIconOperationBlend.BlendType.Add, 0, 0); + result = new DreamValue(iconObj); } else if (second.Value != null) { switch (first.Type) { case DreamValue.DreamValueType.Float when second.Type == DreamValue.DreamValueType.Float: @@ -629,11 +640,6 @@ private static void HandleSuffixPronoun(ref StringBuilder formattedString, ReadO case DreamValue.DreamValueType.String when second.Type == DreamValue.DreamValueType.String: result = new DreamValue(first.GetValueAsString() + second.GetValueAsString()); break; - case DreamValue.DreamValueType.DreamResource when (second.Type == DreamValue.DreamValueType.String && first.TryGetValueAsDreamResource(out var rsc) && rsc.ResourcePath.EndsWith("dmi")): - // TODO icon += hexcolor is the same as Blend() - state.DreamManager.WriteWorldLog("Appending colors to DMIs is not implemented", LogLevel.Warning, "opendream.unimplemented"); - result = first; - break; default: throw new Exception("Invalid append operation on " + first + " and " + second); } diff --git a/OpenDreamRuntime/Procs/DMProc.cs b/OpenDreamRuntime/Procs/DMProc.cs index 9c5582fdff..0260ed861d 100644 --- a/OpenDreamRuntime/Procs/DMProc.cs +++ b/OpenDreamRuntime/Procs/DMProc.cs @@ -3,12 +3,14 @@ using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.MetaObjects; using OpenDreamRuntime.Procs.DebugAdapter; +using OpenDreamRuntime.Resources; using OpenDreamShared.Dream; using OpenDreamShared.Dream.Procs; namespace OpenDreamRuntime.Procs { sealed class DMProc : DreamProc { public readonly IDreamObjectTree ObjectTree; + public readonly DreamResourceManager ResourceManager; public byte[] Bytecode { get; } private readonly int _maxStackSize; @@ -16,9 +18,10 @@ sealed class DMProc : DreamProc { public string? Source { get; set; } public int Line { get; set; } - public DMProc(DreamPath owningType, string name, DreamProc superProc, List argumentNames, List argumentTypes, byte[] bytecode, int maxStackSize, ProcAttributes attributes, string? verbName, string? verbCategory, string? verbDesc, sbyte? invisibility, IDreamObjectTree objectTree) + public DMProc(DreamPath owningType, string name, DreamProc superProc, List argumentNames, List argumentTypes, byte[] bytecode, int maxStackSize, ProcAttributes attributes, string? verbName, string? verbCategory, string? verbDesc, sbyte? invisibility, IDreamObjectTree objectTree, DreamResourceManager resourceManager) : base(owningType, name, superProc, attributes, argumentNames, argumentTypes, verbName, verbCategory, verbDesc, invisibility) { ObjectTree = objectTree; + ResourceManager = resourceManager; Bytecode = bytecode; _maxStackSize = maxStackSize; } diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs index d6dbe8d430..044794603b 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeIcon.cs @@ -1,6 +1,8 @@ using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.MetaObjects; using OpenDreamRuntime.Resources; +using OpenDreamShared.Dream; +using BlendType = OpenDreamRuntime.Objects.DreamIconOperationBlend.BlendType; namespace OpenDreamRuntime.Procs.Native { static class DreamProcNativeIcon { @@ -46,6 +48,17 @@ public static DreamValue NativeProc_Insert(DreamObject instance, DreamObject usr return DreamValue.Null; } + public static void Blend(DreamIcon icon, DreamValue blend, BlendType function, int x, int y) { + if (blend.TryGetValueAsString(out var colorStr)) { + if (!ColorHelpers.TryParseColor(colorStr, out var color)) + throw new Exception($"Invalid color {colorStr}"); + + icon.ApplyOperation(new DreamIconOperationBlendColor(function, x, y, color)); + } else { + icon.ApplyOperation(new DreamIconOperationBlendImage(function, x, y, blend)); + } + } + [DreamProc("Blend")] [DreamProcParameter("icon", Type = DreamValue.DreamValueType.DreamObject)] [DreamProcParameter("function", Type = DreamValue.DreamValueType.Float)] @@ -63,10 +76,7 @@ public static DreamValue NativeProc_Blend(DreamObject instance, DreamObject usr, if (!function.TryGetValueAsInteger(out var functionValue)) throw new Exception($"Invalid 'function' argument {function}"); - var blendType = (DreamIconOperationBlend.BlendType) functionValue; - - DreamIcon iconObj = DreamMetaObjectIcon.ObjectToDreamIcon[instance]; - iconObj.ApplyOperation(new DreamIconOperationBlend(blendType, icon, x, y)); + Blend(DreamMetaObjectIcon.ObjectToDreamIcon[instance], icon, (BlendType)functionValue, x, y); return DreamValue.Null; } From 6fe034ccbd7ea1f0e23d49d6f3294c4c5001f1c5 Mon Sep 17 00:00:00 2001 From: wixoaGit Date: Wed, 14 Dec 2022 14:33:34 -0500 Subject: [PATCH 3/3] Further merge conflict resolving --- OpenDreamRuntime/Procs/DMOpcodeHandlers.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index e2ea656567..088eee116a 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -627,8 +627,8 @@ private static void HandleSuffixPronoun(ref StringBuilder formattedString, ReadO // 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 DreamObject iconObj = state.Proc.ObjectTree.CreateObject(DreamPath.Icon); - var icon = DreamMetaObjectIcon.InitializeIcon(state.Proc.ResourceManager, iconObj); - var from = DreamMetaObjectIcon.GetIconResourceAndDescription(state.Proc.ObjectTree, state.Proc.ResourceManager, first); + var icon = DreamMetaObjectIcon.InitializeIcon(state.Proc.DreamResourceManager, iconObj); + var from = DreamMetaObjectIcon.GetIconResourceAndDescription(state.Proc.ObjectTree, state.Proc.DreamResourceManager, first); icon.InsertStates(from.Resource, from.Description, DreamValue.Null, DreamValue.Null, DreamValue.Null); DreamProcNativeIcon.Blend(icon, second, DreamIconOperationBlend.BlendType.Add, 0, 0);