diff --git a/OpenDreamRuntime/Objects/DreamObject.cs b/OpenDreamRuntime/Objects/DreamObject.cs index 14d030aa3b..80b7654b41 100644 --- a/OpenDreamRuntime/Objects/DreamObject.cs +++ b/OpenDreamRuntime/Objects/DreamObject.cs @@ -149,11 +149,12 @@ public bool IsSubtypeOf(TreeEntry ancestor) { } #region Variables + public virtual bool IsSaved(string name) { return ObjectDefinition.Variables.ContainsKey(name) && !ObjectDefinition.GlobalVariables.ContainsKey(name) - && !(ObjectDefinition.ConstVariables is not null && ObjectDefinition.ConstVariables.Contains(name)) - && !(ObjectDefinition.TmpVariables is not null && ObjectDefinition.TmpVariables.Contains(name)); + && !(ObjectDefinition.ConstVariables.Contains(name)) + && !(ObjectDefinition.TmpVariables.Contains(name)); } public bool HasVariable(string name) { diff --git a/OpenDreamRuntime/Objects/DreamObjectDefinition.cs b/OpenDreamRuntime/Objects/DreamObjectDefinition.cs index 670299e238..3f073d7c87 100644 --- a/OpenDreamRuntime/Objects/DreamObjectDefinition.cs +++ b/OpenDreamRuntime/Objects/DreamObjectDefinition.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.Collections.Frozen; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using OpenDreamRuntime.Procs; using OpenDreamRuntime.Rendering; @@ -51,12 +52,15 @@ public bool NoConstructors { // Maps variables from their name to their initial value. public readonly Dictionary Variables = new(); + // Maps /static variables from name to their index in the global variable table. - public readonly Dictionary GlobalVariables = new(); + public FrozenDictionary GlobalVariables = FrozenDictionary.Empty; + // Contains hashes of variables that are tagged /const. - public HashSet? ConstVariables = null; + public FrozenSet ConstVariables = FrozenSet.Empty; + // Contains hashes of variables that are tagged /tmp. - public HashSet? TmpVariables = null; + public FrozenSet TmpVariables = FrozenSet.Empty; public DreamObjectDefinition(DreamObjectDefinition copyFrom) { DreamManager = copyFrom.DreamManager; @@ -79,9 +83,9 @@ public DreamObjectDefinition(DreamObjectDefinition copyFrom) { InitializationProc = copyFrom.InitializationProc; Variables = new Dictionary(copyFrom.Variables); - GlobalVariables = new Dictionary(copyFrom.GlobalVariables); - ConstVariables = copyFrom.ConstVariables is not null ? new HashSet(copyFrom.ConstVariables) : null; - TmpVariables = copyFrom.TmpVariables is not null ? new HashSet(copyFrom.TmpVariables) : null; + GlobalVariables = copyFrom.GlobalVariables; + ConstVariables = copyFrom.ConstVariables; + TmpVariables = copyFrom.TmpVariables; Procs = new Dictionary(copyFrom.Procs); OverridingProcs = new Dictionary(copyFrom.OverridingProcs); if (copyFrom.Verbs != null) @@ -113,11 +117,9 @@ public DreamObjectDefinition(DreamManager dreamManager, DreamObjectTree objectTr if (Parent.Verbs != null) Verbs = new List(Parent.Verbs); if (Parent != ObjectTree.Root.ObjectDefinition) // Don't include root-level globals - GlobalVariables = new Dictionary(Parent.GlobalVariables); - if (Parent.ConstVariables != null) - ConstVariables = new HashSet(Parent.ConstVariables); - if (Parent.TmpVariables != null) - TmpVariables = new HashSet(Parent.TmpVariables); + GlobalVariables = Parent.GlobalVariables; + ConstVariables = Parent.ConstVariables; + TmpVariables = Parent.TmpVariables; } } diff --git a/OpenDreamRuntime/Objects/DreamObjectTree.cs b/OpenDreamRuntime/Objects/DreamObjectTree.cs index f2492129f4..8a86ff85b3 100644 --- a/OpenDreamRuntime/Objects/DreamObjectTree.cs +++ b/OpenDreamRuntime/Objects/DreamObjectTree.cs @@ -1,3 +1,4 @@ +using System.Collections.Frozen; using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Threading.Tasks; @@ -47,8 +48,8 @@ public sealed class DreamObjectTree { public TreeEntry Obj { get; private set; } public TreeEntry Mob { get; private set; } - private readonly Dictionary _pathToType = new(); - private Dictionary _globalProcIds; + private FrozenDictionary _pathToType = FrozenDictionary.Empty; + private FrozenDictionary _globalProcIds = FrozenDictionary.Empty; [Dependency] private readonly AtomManager _atomManager = default!; [Dependency] private readonly DreamManager _dreamManager = default!; @@ -124,13 +125,15 @@ public bool TryGetGlobalProc(string name, [NotNullWhen(true)] out DreamProc? glo public IEnumerable GetAllDescendants(TreeEntry treeEntry) { yield return treeEntry; - foreach (int typeId in treeEntry.InheritingTypes) { - TreeEntry type = Types[typeId]; - IEnumerator typeChildren = GetAllDescendants(type).GetEnumerator(); + if (treeEntry.InheritingTypes is not null) { + foreach (int typeId in treeEntry.InheritingTypes) { + TreeEntry type = Types[typeId]; + IEnumerator typeChildren = GetAllDescendants(type).GetEnumerator(); - while (typeChildren.MoveNext()) yield return typeChildren.Current; + while (typeChildren.MoveNext()) yield return typeChildren.Current; - typeChildren.Dispose(); + typeChildren.Dispose(); + } } } @@ -265,6 +268,8 @@ public DreamValue GetDreamValueFromJsonElement(object? value) { private void LoadTypesFromJson(DreamTypeJson[] types, ProcDefinitionJson[]? procs, int[]? globalProcs) { Types = new TreeEntry[types.Length]; + var pathToTypes = new Dictionary(types.Length); + //First pass: Create types and set them up for initialization Types[0] = Root; for (int i = 1; i < Types.Length; i++) { @@ -272,9 +277,11 @@ private void LoadTypesFromJson(DreamTypeJson[] types, ProcDefinitionJson[]? proc var type = new TreeEntry(path, i); Types[i] = type; - _pathToType[path] = type; + pathToTypes[path] = type; } + _pathToType = pathToTypes.ToFrozenDictionary(); + World = GetTreeEntry("/world"); List = GetTreeEntry("/list"); Client = GetTreeEntry("/client"); @@ -307,7 +314,7 @@ private void LoadTypesFromJson(DreamTypeJson[] types, ProcDefinitionJson[]? proc if (jsonType.Parent != null) { TreeEntry parent = Types[jsonType.Parent.Value]; - + parent.InheritingTypes ??= new List(1); parent.InheritingTypes.Add(i); type.ParentEntry = parent; } @@ -387,23 +394,30 @@ private void LoadVariablesFromJson(DreamObjectDefinition objectDefinition, Dream } if (jsonObject.GlobalVariables != null) { + Dictionary globalVars = new Dictionary(jsonObject.GlobalVariables.Count); foreach (KeyValuePair jsonGlobalVariable in jsonObject.GlobalVariables) { - objectDefinition.GlobalVariables.Add(jsonGlobalVariable.Key, jsonGlobalVariable.Value); + globalVars.Add(jsonGlobalVariable.Key, jsonGlobalVariable.Value); } + + objectDefinition.GlobalVariables = globalVars.ToFrozenDictionary(); } if (jsonObject.ConstVariables != null) { - objectDefinition.ConstVariables ??= new(); + HashSet constVars = new HashSet(jsonObject.ConstVariables.Count); foreach (string jsonConstVariable in jsonObject.ConstVariables) { - objectDefinition.ConstVariables.Add(jsonConstVariable); + constVars.Add(jsonConstVariable); } + + objectDefinition.ConstVariables = constVars.ToFrozenSet(); } if(jsonObject.TmpVariables != null) { - objectDefinition.TmpVariables ??= new(); + HashSet tmpVars = new HashSet(jsonObject.TmpVariables.Count); foreach (string jsonTmpVariable in jsonObject.TmpVariables) { - objectDefinition.TmpVariables.Add(jsonTmpVariable); + tmpVars.Add(jsonTmpVariable); } + + objectDefinition.TmpVariables = tmpVars.ToFrozenSet(); } } @@ -426,13 +440,15 @@ private void LoadProcsFromJson(ProcDefinitionJson[]? jsonProcs, int[]? jsonGloba } if (jsonGlobalProcs != null) { - _globalProcIds = new(jsonGlobalProcs.Length); + Dictionary globalProcIds = new(jsonGlobalProcs.Length); foreach (var procId in jsonGlobalProcs) { var proc = Procs[procId]; - _globalProcIds.Add(proc.Name, procId); + globalProcIds.Add(proc.Name, procId); } + + _globalProcIds = globalProcIds.ToFrozenDictionary(); } } @@ -454,14 +470,14 @@ private AsyncNativeProc CreateAsyncNativeProc(TreeEntry owningType, Func> func) { var (name, defaultArgumentValues, argumentNames) = NativeProc.GetNativeInfo(func); - var proc = new AsyncNativeProc(_globalProcIds[name], Root, name, argumentNames, defaultArgumentValues, func); + var proc = new AsyncNativeProc(_globalProcIds![name], Root, name, argumentNames, defaultArgumentValues, func); Procs[proc.Id] = proc; } @@ -482,11 +498,13 @@ public void SetNativeProc(TreeEntry type, Func private IEnumerable TraversePostOrder(TreeEntry from) { - foreach (int typeId in from.InheritingTypes) { - TreeEntry type = Types[typeId]; - using IEnumerator typeChildren = TraversePostOrder(type).GetEnumerator(); + if (from.InheritingTypes is not null) { + foreach (int typeId in from.InheritingTypes) { + TreeEntry type = Types[typeId]; + using IEnumerator typeChildren = TraversePostOrder(type).GetEnumerator(); - while (typeChildren.MoveNext()) yield return typeChildren.Current; + while (typeChildren.MoveNext()) yield return typeChildren.Current; + } } yield return from; @@ -499,7 +517,7 @@ public sealed class TreeEntry { public readonly int Id; public DreamObjectDefinition ObjectDefinition; public TreeEntry ParentEntry; - public readonly List InheritingTypes = new(); + public List? InheritingTypes; /// /// This node's index in the inheritance tree based on a depth-first search
diff --git a/OpenDreamRuntime/Objects/Types/DreamList.cs b/OpenDreamRuntime/Objects/Types/DreamList.cs index 31d66392a5..8ac354f02c 100644 --- a/OpenDreamRuntime/Objects/Types/DreamList.cs +++ b/OpenDreamRuntime/Objects/Types/DreamList.cs @@ -453,7 +453,7 @@ public DreamGlobalVars(DreamObjectDefinition listDef) : base(listDef, 0) { public override List GetValues() { var root = _objectTree.Root.ObjectDefinition; - List values = new List(root.GlobalVariables.Keys.Count - 1); + List values = new List(root.GlobalVariables.Keys.Length - 1); // Skip world foreach (var key in root.GlobalVariables.Keys.Skip(1)) { values.Add(new DreamValue(key)); diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index 1ee37031ae..56a92ed0db 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -2227,8 +2227,8 @@ public static ProcStatus IsSaved(DMProcState state) { } if (objectDefinition.GlobalVariables.ContainsKey(property) - || (objectDefinition.ConstVariables is not null && objectDefinition.ConstVariables.Contains(property)) - || (objectDefinition.TmpVariables is not null && objectDefinition.TmpVariables.Contains(property))) { + || (objectDefinition.ConstVariables.Contains(property)) + || (objectDefinition.TmpVariables.Contains(property))) { state.Push(new DreamValue(0)); } else { state.Push(new DreamValue(1));