diff --git a/DMCompiler/Bytecode/DreamProcOpcode.cs b/DMCompiler/Bytecode/DreamProcOpcode.cs
index e73e513fe8..59ab997b4b 100644
--- a/DMCompiler/Bytecode/DreamProcOpcode.cs
+++ b/DMCompiler/Bytecode/DreamProcOpcode.cs
@@ -137,7 +137,8 @@ public enum DreamProcOpcode : byte {
CreateFilteredListEnumerator = 0x41,
[OpcodeMetadata(-1)]
Power = 0x42,
- //0x43,
+ [OpcodeMetadata(-1, OpcodeArgType.EnumeratorId, OpcodeArgType.FilterId)]
+ CreateFilteredBaseTypesListEnumerator = 0x43,
//0x44
[OpcodeMetadata(-3, OpcodeArgType.TypeId)]
Prompt = 0x45,
diff --git a/DMCompiler/DM/Builders/DMProcBuilder.cs b/DMCompiler/DM/Builders/DMProcBuilder.cs
index a52cd9265f..2ed16a4199 100644
--- a/DMCompiler/DM/Builders/DMProcBuilder.cs
+++ b/DMCompiler/DM/Builders/DMProcBuilder.cs
@@ -602,8 +602,10 @@ public void ProcessStatementForList(DMExpression list, DMExpression outputVar, D
lValue = null;
}
+ list.EmitPushValue(dmObject, proc);
// Depending on the var's type and possibly a given "as [types]", an implicit istype() check is performed
DreamPath? implicitTypeCheck = null;
+ bool alreadyCreatedEnumerator = false; // Keep track of if we did CreateFilteredBaseTypesListEnumerator already
if (dmTypes == null) {
// No "as" means the var's type will be used
implicitTypeCheck = lValue?.Path;
@@ -611,23 +613,25 @@ public void ProcessStatementForList(DMExpression list, DMExpression outputVar, D
// "as /datum" will perform a check for /datum
implicitTypeCheck = dmTypes.Value.TypePath;
} else if (!dmTypes.Value.IsAnything) {
- // "as anything" performs no check. Other values are unimplemented.
- DMCompiler.UnimplementedWarning(outputVar.Location,
- $"As type {dmTypes} in for loops is unimplemented. No type check will be performed.");
+ // "as anything" performs no check. Other than that, all that's left are type assignments like `as mob|obj|area`.
+ proc.CreateFilteredBaseTypesListEnumerator((byte) dmTypes.Value.Type, dmTypes.Value.Type);
+ alreadyCreatedEnumerator = true;
+ DMCompiler.VerbosePrint($"Created CreateFilteredBaseTypesListEnumerator");
}
- list.EmitPushValue(dmObject, proc);
- if (implicitTypeCheck != null) {
- if (DMObjectTree.TryGetTypeId(implicitTypeCheck.Value, out var filterTypeId)) {
- // Create an enumerator that will do the implicit istype() for us
- proc.CreateFilteredListEnumerator(filterTypeId, implicitTypeCheck.Value);
+ if (!alreadyCreatedEnumerator) {
+ if (implicitTypeCheck != null) {
+ if (DMObjectTree.TryGetTypeId(implicitTypeCheck.Value, out var filterTypeId)) {
+ // Create an enumerator that will do the implicit istype() for us
+ proc.CreateFilteredListEnumerator(filterTypeId, implicitTypeCheck.Value);
+ } else {
+ DMCompiler.Emit(WarningCode.ItemDoesntExist, outputVar.Location,
+ $"Cannot filter enumeration by type {implicitTypeCheck.Value}, it does not exist");
+ proc.CreateListEnumerator();
+ }
} else {
- DMCompiler.Emit(WarningCode.ItemDoesntExist, outputVar.Location,
- $"Cannot filter enumeration by type {implicitTypeCheck.Value}, it does not exist");
proc.CreateListEnumerator();
}
- } else {
- proc.CreateListEnumerator();
}
proc.StartScope();
diff --git a/DMCompiler/DM/DMObject.cs b/DMCompiler/DM/DMObject.cs
index ea65433a88..52172642b8 100644
--- a/DMCompiler/DM/DMObject.cs
+++ b/DMCompiler/DM/DMObject.cs
@@ -230,4 +230,8 @@ public DMValueType GetDMValueType() {
return DMValueType.Anything;
}
+
+ public DreamPath[] GetDMAncestors() {
+
+ }
}
diff --git a/DMCompiler/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs
index 8c1e5be54d..3a18443691 100644
--- a/DMCompiler/DM/DMProc.cs
+++ b/DMCompiler/DM/DMProc.cs
@@ -372,6 +372,12 @@ public void CreateFilteredListEnumerator(int filterTypeId, DreamPath filterType)
WriteFilterID(filterTypeId, filterType);
}
+ public void CreateFilteredBaseTypesListEnumerator(byte filterTypeId, DMValueType filterType) {
+ WriteOpcode(DreamProcOpcode.CreateFilteredBaseTypesListEnumerator);
+ WriteEnumeratorId(_enumeratorIdCounter++);
+ WriteFilterTypeID(filterTypeId, filterType);
+ }
+
public void CreateTypeEnumerator() {
WriteOpcode(DreamProcOpcode.CreateTypeEnumerator);
WriteEnumeratorId(_enumeratorIdCounter++);
@@ -1115,6 +1121,10 @@ private void WriteFilterID(int filterId, DreamPath filter) {
AnnotatedBytecode.WriteFilterId(filterId, filter, _writerLocation);
}
+ private void WriteFilterTypeID(byte filterId, DMValueType filter) {
+ AnnotatedBytecode.WriteFilterTypeId(filterId, filter, _writerLocation);
+ }
+
private void WriteStackDelta(int delta) {
AnnotatedBytecode.WriteStackDelta(delta, _writerLocation);
}
diff --git a/DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs b/DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs
index 9009df1ecf..903b8aa67f 100644
--- a/DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs
+++ b/DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs
@@ -152,6 +152,26 @@ public void WriteFilterId(int filterTypeId, DreamPath filterPath, Location locat
_annotatedBytecode[^1].AddArg(new AnnotatedBytecodeFilter(filterTypeId, filterPath, location));
}
+ ///
+ /// Write a filter. Filters are stored as reference IDs in the raw bytecode, which refer
+ /// to a string in the string table containing the datum path of the filter.
+ ///
+ /// The type ID of the filter
+ /// /// The base types of the filter
+ /// The location of the filter in the source code
+ ///
+ public void WriteFilterTypeId(byte filterTypeId, DMValueType filterTypes, Location location) {
+ _location = location;
+
+ if (_requiredArgs.Count == 0 || _requiredArgs.Peek() != OpcodeArgType.FilterId) {
+ DMCompiler.ForcedError(location, "Expected filter argument");
+ }
+
+ _requiredArgs.Pop();
+
+ _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeTypeFilter(filterTypeId, filterTypes, location));
+ }
+
///
/// Write a list size, restricted to non-negative integers
///
diff --git a/DMCompiler/Optimizer/AnnotatedBytecode.cs b/DMCompiler/Optimizer/AnnotatedBytecode.cs
index 35dbd2fab0..4faac4159a 100644
--- a/DMCompiler/Optimizer/AnnotatedBytecode.cs
+++ b/DMCompiler/Optimizer/AnnotatedBytecode.cs
@@ -574,6 +574,35 @@ public Location GetLocation() {
}
}
+internal sealed class AnnotatedBytecodeTypeFilter : IAnnotatedBytecode {
+ public DMValueType FilterPath;
+
+ public int FilterTypeId;
+ public Location Location;
+
+ public AnnotatedBytecodeTypeFilter(int filterTypeId, DMValueType filterPath, Location location) {
+ FilterTypeId = filterTypeId;
+ FilterPath = filterPath;
+ Location = location;
+ }
+
+ public void AddArg(IAnnotatedBytecode arg) {
+ DMCompiler.ForcedError(Location, "Cannot add args to a filter");
+ }
+
+ public void SetLocation(IAnnotatedBytecode loc) {
+ Location = loc.GetLocation();
+ }
+
+ public void SetLocation(Location loc) {
+ Location = loc;
+ }
+
+ public Location GetLocation() {
+ return Location;
+ }
+}
+
internal sealed class AnnotatedBytecodeReference : IAnnotatedBytecode {
public int Index;
public Location Location;
diff --git a/DMCompiler/Optimizer/AnnotatedBytecodeSerializer.cs b/DMCompiler/Optimizer/AnnotatedBytecodeSerializer.cs
index 9d92355fae..cb1dda3412 100644
--- a/DMCompiler/Optimizer/AnnotatedBytecodeSerializer.cs
+++ b/DMCompiler/Optimizer/AnnotatedBytecodeSerializer.cs
@@ -140,6 +140,9 @@ private void SerializeInstruction(AnnotatedBytecodeInstruction instruction) {
case AnnotatedBytecodeTypeId annotatedBytecodeTypeId:
_bytecodeWriter.Write(annotatedBytecodeTypeId.TypeId);
break;
+ case AnnotatedBytecodeTypeFilter annotatedBytecodeTypeFilter:
+ _bytecodeWriter.Write(annotatedBytecodeTypeFilter.FilterTypeId);
+ break;
default:
throw new ArgumentOutOfRangeException();
}
diff --git a/OpenDreamRuntime/Objects/DreamObject.cs b/OpenDreamRuntime/Objects/DreamObject.cs
index 912ba49781..7c81f8d930 100644
--- a/OpenDreamRuntime/Objects/DreamObject.cs
+++ b/OpenDreamRuntime/Objects/DreamObject.cs
@@ -120,6 +120,10 @@ public bool IsSubtypeOf(TreeEntry ancestor) {
return ObjectDefinition.IsSubtypeOf(ancestor);
}
+ public bool IsSubtypeOf(byte ancestors) {
+ return ObjectDefinition.IsSubtypeOf(ancestors);
+ }
+
#region Variables
public virtual bool IsSaved(string name) {
return ObjectDefinition.Variables.ContainsKey(name)
diff --git a/OpenDreamRuntime/Objects/DreamObjectDefinition.cs b/OpenDreamRuntime/Objects/DreamObjectDefinition.cs
index 670299e238..eebd3a104c 100644
--- a/OpenDreamRuntime/Objects/DreamObjectDefinition.cs
+++ b/OpenDreamRuntime/Objects/DreamObjectDefinition.cs
@@ -197,4 +197,9 @@ public bool TryGetVariable(string varName, out DreamValue value) {
public bool IsSubtypeOf(TreeEntry ancestor) {
return TreeEntry.IsSubtypeOf(ancestor);
}
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsSubtypeOf(byte ancestors) {
+ return TreeEntry.IsSubtypeOf(ancestors);
+ }
}
diff --git a/OpenDreamRuntime/Objects/DreamObjectTree.cs b/OpenDreamRuntime/Objects/DreamObjectTree.cs
index ce86bb4c33..bf351c0ac6 100644
--- a/OpenDreamRuntime/Objects/DreamObjectTree.cs
+++ b/OpenDreamRuntime/Objects/DreamObjectTree.cs
@@ -516,6 +516,11 @@ public bool IsSubtypeOf(TreeEntry ancestor) {
return (TreeIndex - ancestor.TreeIndex) <= ancestor.ChildCount;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsSubtypeOf(byte ancestors) {
+ return true;
+ }
+
public override string ToString() {
return Path;
}
diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs
index 34ec285840..c143fdfe25 100644
--- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs
+++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs
@@ -107,6 +107,36 @@ private static IDreamValueEnumerator GetContentsEnumerator(DreamObjectTree objec
return new DreamValueArrayEnumerator(Array.Empty());
}
+ private static IDreamValueEnumerator GetContentsEnumeratorAeiou(DreamObjectTree objectTree, AtomManager atomManager, DreamValue value, byte? filterType) {
+ if (!value.TryGetValueAsDreamList(out var list)) {
+ if (value.TryGetValueAsDreamObject(out var dreamObject)) {
+ if (dreamObject == null)
+ return new DreamValueArrayEnumerator(Array.Empty());
+
+ if (dreamObject is DreamObjectAtom) {
+ list = dreamObject.GetVariable("contents").MustGetValueAsDreamList();
+ } else if (dreamObject is DreamObjectWorld) {
+ return new WorldContentsEnumerator(atomManager, filterType);
+ }
+ }
+ }
+
+ if (list != null) {
+ // world.contents has its own special enumerator to prevent the huge copy
+ if (list is WorldContentsList)
+ return new WorldContentsEnumerator(atomManager, filterType);
+
+ var values = list.GetValues().ToArray();
+
+ return filterType == null
+ ? new DreamValueArrayEnumerator(values)
+ : new FilteredDreamValueArrayEnumeratorAeiou(values, (byte) filterType);
+ }
+
+ // BYOND ignores all floats, strings, types, etc. here and just doesn't run the loop.
+ return new DreamValueArrayEnumerator(Array.Empty());
+ }
+
public static ProcStatus CreateListEnumerator(DMProcState state) {
var enumeratorId = state.ReadInt();
var enumerator = GetContentsEnumerator(state.Proc.ObjectTree, state.Proc.AtomManager, state.Pop(), null);
@@ -125,6 +155,16 @@ public static ProcStatus CreateFilteredListEnumerator(DMProcState state) {
return ProcStatus.Continue;
}
+ public static ProcStatus CreateFilteredBaseTypesListEnumerator(DMProcState state) {
+ var enumeratorId = state.ReadInt();
+ var filterTypeId = state.ReadByte();
+ var filterType = state.Proc.ObjectTree.GetTreeEntry(filterTypeId);
+ var enumerator = GetContentsEnumeratorAeiou(state.Proc.ObjectTree, state.Proc.AtomManager, state.Pop(), filterType);
+
+ state.Enumerators[enumeratorId] = enumerator;
+ return ProcStatus.Continue;
+ }
+
public static ProcStatus CreateTypeEnumerator(DMProcState state) {
var enumeratorId = state.ReadInt();
var typeValue = state.Pop();
diff --git a/OpenDreamRuntime/Procs/DMProc.cs b/OpenDreamRuntime/Procs/DMProc.cs
index 4b83a2477b..825a665418 100644
--- a/OpenDreamRuntime/Procs/DMProc.cs
+++ b/OpenDreamRuntime/Procs/DMProc.cs
@@ -242,6 +242,7 @@ public sealed class DMProcState : ProcState {
{DreamProcOpcode.OutputControl, DMOpcodeHandlers.OutputControl},
{DreamProcOpcode.BitShiftRight, DMOpcodeHandlers.BitShiftRight},
{DreamProcOpcode.CreateFilteredListEnumerator, DMOpcodeHandlers.CreateFilteredListEnumerator},
+ {DreamProcOpcode.CreateFilteredBaseTypesListEnumerator, DMOpcodeHandlers.CreateFilteredBaseTypesListEnumerator},
{DreamProcOpcode.Power, DMOpcodeHandlers.Power},
{DreamProcOpcode.Prompt, DMOpcodeHandlers.Prompt},
{DreamProcOpcode.Ftp, DMOpcodeHandlers.Ftp},
diff --git a/OpenDreamRuntime/Procs/DreamEnumerators.cs b/OpenDreamRuntime/Procs/DreamEnumerators.cs
index b954ea507b..c47d4ec9dd 100644
--- a/OpenDreamRuntime/Procs/DreamEnumerators.cs
+++ b/OpenDreamRuntime/Procs/DreamEnumerators.cs
@@ -84,7 +84,7 @@ public bool Enumerate(DMProcState state, DreamReference? reference) {
///
/// Enumerates over an array of DreamValues, filtering for a certain type
-/// for (var/obj/item/I in contents)
+/// for (var/I as obj|mob in contents)
///
internal sealed class FilteredDreamValueArrayEnumerator : IDreamValueEnumerator {
private readonly DreamValue[] _dreamValueArray;
@@ -115,6 +115,39 @@ public bool Enumerate(DMProcState state, DreamReference? reference) {
}
}
+///
+/// Enumerates over an array of DreamValues, filtering for a certain type
+/// for (var/I as obj|mob in contents)
+///
+internal sealed class FilteredDreamValueArrayEnumeratorAeiou : IDreamValueEnumerator {
+ private readonly DreamValue[] _dreamValueArray;
+ private readonly byte _filterType;
+ private int _current = -1;
+
+ public FilteredDreamValueArrayEnumeratorAeiou(DreamValue[] dreamValueArray, byte filterType) {
+ _dreamValueArray = dreamValueArray;
+ _filterType = filterType;
+ }
+
+ public bool Enumerate(DMProcState state, DreamReference? reference) {
+ do {
+ _current++;
+ if (_current >= _dreamValueArray.Length) {
+ if (reference != null)
+ state.AssignReference(reference.Value, DreamValue.Null);
+ return false;
+ }
+
+ DreamValue value = _dreamValueArray[_current];
+ if (value.TryGetValueAsDreamObject(out var dreamObject) && (dreamObject?.IsSubtypeOf(_filterType) ?? false)) {
+ if (reference != null)
+ state.AssignReference(reference.Value, value);
+ return true;
+ }
+ } while (true);
+ }
+}
+
///
/// Enumerates over all atoms in the world, possibly filtering for a certain type
/// for (var/obj/item/I in world)
diff --git a/OpenDreamRuntime/Procs/ProcDecoder.cs b/OpenDreamRuntime/Procs/ProcDecoder.cs
index f5725b082a..dc1eaf3a6d 100644
--- a/OpenDreamRuntime/Procs/ProcDecoder.cs
+++ b/OpenDreamRuntime/Procs/ProcDecoder.cs
@@ -162,6 +162,7 @@ public ITuple DecodeInstruction() {
return (opcode, ReadInt(), ReadReference(), ReadInt());
case DreamProcOpcode.CreateFilteredListEnumerator:
+ case DreamProcOpcode.CreateFilteredBaseTypesListEnumerator:
case DreamProcOpcode.EnumerateNoAssign:
return (opcode, ReadInt(), ReadInt());
@@ -289,6 +290,12 @@ or DreamProcOpcode.JumpIfTrueReference
text.Append(getTypePath(type));
break;
+ case (DreamProcOpcode.CreateFilteredBaseTypesListEnumerator, int enumeratorId, int type):
+ text.Append(enumeratorId);
+ text.Append(' ');
+ text.Append(getTypePath(type));
+ break;
+
case (DreamProcOpcode.CreateListNRefs
or DreamProcOpcode.PushNRefs, DMReference[] refs): {
foreach (var reference in refs) {