diff --git a/Content.Tests/DMTests.cs b/Content.Tests/DMTests.cs index bb3a63dbb1..90cff4dc8b 100644 --- a/Content.Tests/DMTests.cs +++ b/Content.Tests/DMTests.cs @@ -131,7 +131,7 @@ public void TestFiles(string sourceFile, DMTestFlags testFlags, int errorCode) { Assert.Fail("No global proc named RunTest"); return DreamValue.Null; } - }); + }).Dispose(); var watch = new Stopwatch(); watch.Start(); diff --git a/DMCompiler/Compiler/DM/DMParser.cs b/DMCompiler/Compiler/DM/DMParser.cs index d7b6fa3f10..ce2732b4ec 100644 --- a/DMCompiler/Compiler/DM/DMParser.cs +++ b/DMCompiler/Compiler/DM/DMParser.cs @@ -2761,7 +2761,7 @@ private void BracketWhitespace() { if (callParameters.Length is < 1) Emit(WarningCode.InvalidArgumentCount, callLoc, "Expected at least 1 argument for animate()"); - + return new DMASTAnimate(identifier.Location, callParameters); } default: diff --git a/DMCompiler/DM/DMCodeTree.Vars.cs b/DMCompiler/DM/DMCodeTree.Vars.cs index bdb30ec3b9..5f8565d2ba 100644 --- a/DMCompiler/DM/DMCodeTree.Vars.cs +++ b/DMCompiler/DM/DMCodeTree.Vars.cs @@ -148,6 +148,7 @@ private bool HandleGlobalVar(DMCompiler compiler, DMObject dmObject, int pass) { compiler.GlobalInitProc.DebugSource(value.Location); value.EmitPushValue(new(compiler, dmObject, compiler.GlobalInitProc)); compiler.GlobalInitProc.Assign(DMReference.CreateGlobal(globalId)); + compiler.GlobalInitProc.Pop(); return true; } @@ -298,6 +299,7 @@ public override bool TryDefineVar(DMCompiler compiler, int pass) { compiler.GlobalInitProc.DebugSource(value.Location); value.EmitPushValue(new(compiler, dmObject, compiler.GlobalInitProc)); compiler.GlobalInitProc.Assign(DMReference.CreateGlobal(globalId)); + compiler.GlobalInitProc.Pop(); } return true; diff --git a/DMCompiler/DM/DMObject.cs b/DMCompiler/DM/DMObject.cs index 39964c217d..af1a7bca3e 100644 --- a/DMCompiler/DM/DMObject.cs +++ b/DMCompiler/DM/DMObject.cs @@ -169,6 +169,7 @@ public void CreateInitializationProc() { foreach (var assignment in InitializationProcAssignments) { init.DebugSource(assignment.Assignment.Location); assignment.Assignment.EmitPushValue(new(compiler, this, init)); + init.Pop(); } } diff --git a/DMCompiler/DMStandard/Types/Datum.dm b/DMCompiler/DMStandard/Types/Datum.dm index c14bfb2a23..483a202509 100644 --- a/DMCompiler/DMStandard/Types/Datum.dm +++ b/DMCompiler/DMStandard/Types/Datum.dm @@ -7,8 +7,6 @@ var/tag proc/New() - //SAFETY: If you redefine this to anything except empty, please revisit how DreamObject handles Del() or it will - // attempt to run DM on a GC thread, potentially causing problems. proc/Del() proc/Topic(href, href_list) diff --git a/DMCompiler/DMStandard/_Standard.dm b/DMCompiler/DMStandard/_Standard.dm index 4e151cefc4..0275de35d6 100644 --- a/DMCompiler/DMStandard/_Standard.dm +++ b/DMCompiler/DMStandard/_Standard.dm @@ -82,6 +82,7 @@ proc/rand(L, H) as num proc/rand_seed(Seed) as null proc/range(Dist, Center) as /list|null // NOTE: Not sure if return types have BYOND parity proc/ref(Object) as text +proc/refcount(var/Object) as num proc/replacetext(Haystack, Needle, Replacement, Start = 1, End = 0) as text|null proc/replacetext_char(Haystack, Needle, Replacement, Start = 1, End = 0) as text|null proc/replacetextEx(Haystack, Needle, Replacement, Start = 1, End = 0) as text|null @@ -227,9 +228,3 @@ proc/lentext(T) as num proc/winshow(player, window, show=1) winset(player, window, "is-visible=[show ? "true" : "false"]") - -proc/refcount(var/Object) as num - // woah that's a lot of refs - // i wonder if it's true?? - return 100 - // (it's not) diff --git a/OpenDreamRuntime/AtomManager.cs b/OpenDreamRuntime/AtomManager.cs index 41e39fda49..287adf83f7 100644 --- a/OpenDreamRuntime/AtomManager.cs +++ b/OpenDreamRuntime/AtomManager.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using JetBrains.Annotations; using OpenDreamRuntime.Map; using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.Types; @@ -344,10 +345,12 @@ public void SetAppearanceVar(MutableAppearance appearance, string varName, Dream } //TODO THIS IS A SUPER NASTY HACK + [MustDisposeResource] public DreamValue GetAppearanceVar(MutableAppearance appearance, string varName) { return GetAppearanceVar(new ImmutableAppearance(appearance, null), varName); } + [MustDisposeResource] public DreamValue GetAppearanceVar(ImmutableAppearance appearance, string varName) { switch (varName) { case "name": @@ -692,6 +695,34 @@ public MutableAppearance GetAppearanceFromDefinition(DreamObjectDefinition def) } _definitionAppearanceCache.Add(def, appearance); + nameVar.Dispose(); + descVar.Dispose(); + iconVar.Dispose(); + stateVar.Dispose(); + colorVar.Dispose(); + alphaVar.Dispose(); + glideSizeVar.Dispose(); + dirVar.Dispose(); + invisibilityVar.Dispose(); + opacityVar.Dispose(); + mouseVar.Dispose(); + xVar.Dispose(); + yVar.Dispose(); + layerVar.Dispose(); + planeVar.Dispose(); + renderSourceVar.Dispose(); + renderTargetVar.Dispose(); + blendModeVar.Dispose(); + appearanceFlagsVar.Dispose(); + maptextVar.Dispose(); + maptextWidthVar.Dispose(); + maptextHeightVar.Dispose(); + maptextXVar.Dispose(); + maptextYVar.Dispose(); + mouseOverPointer.Dispose(); + mouseDragPointer.Dispose(); + mouseDropPointer.Dispose(); + mouseDropZone.Dispose(); return appearance; } diff --git a/OpenDreamRuntime/ByondApi/ByondApi.Functions.cs b/OpenDreamRuntime/ByondApi/ByondApi.Functions.cs index b43fc567e1..ed1135d5a8 100644 --- a/OpenDreamRuntime/ByondApi/ByondApi.Functions.cs +++ b/OpenDreamRuntime/ByondApi/ByondApi.Functions.cs @@ -21,7 +21,9 @@ public static unsafe partial class ByondApi { */ [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] private static byte ByondValue_IsTrue(CByondValue* v) { - return ValueFromDreamApi(*v).IsTruthy() ? (byte)1 : (byte)0; + using var value = ValueFromDreamApi(*v); + + return value.IsTruthy() ? (byte)1 : (byte)0; } /** byondapi.h comment: @@ -32,8 +34,8 @@ private static byte ByondValue_IsTrue(CByondValue* v) { */ [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] private static byte ByondValue_Equals(CByondValue* a, CByondValue* b) { - var left = ValueFromDreamApi(*a); - var right = ValueFromDreamApi(*b); + using var left = ValueFromDreamApi(*a); + using var right = ValueFromDreamApi(*b); return DMOpcodeHandlers.IsEqual(left, right) ? (byte)1 : (byte)0; } @@ -85,7 +87,7 @@ private static CByondValue Byond_ThreadSync(delegate* unmanaged[Cdecl] 0) { - return RunOnMainThread(() => callback(data)); + return RunOnMainThread(_ => callback(data)); } RunOnMainThreadNonBlocking(() => callback(data)); @@ -109,7 +111,7 @@ private static uint Byond_GetStrId(byte* cstr) { return NONE; } - return RunOnMainThread(() => { + return RunOnMainThread(_ => { var strId = _refManager!.FindStringId(str); if (strId != null) { return strId.Value; @@ -136,7 +138,7 @@ private static uint Byond_AddGetStrId(byte* cstr) { return NONE; } - return RunOnMainThread(() => { + return RunOnMainThread(_ => { var strIdx = _refManager!.GetRef(str); return strIdx; }); @@ -155,13 +157,13 @@ private static byte Byond_ReadVar(CByondValue* loc, byte* varname, CByondValue* if (loc == null || varname == null || result == null) return SetLastError("loc, varname, or result argument was a null pointer"); - return RunOnMainThread(() => { + return RunOnMainThread(calledFromMain => { try { string? varName = Marshal.PtrToStringUTF8((nint)varname); if (varName == null) return SetLastError("varname argument was a null pointer"); - DreamValue srcValue = ValueFromDreamApi(*loc); + using var srcValue = ValueFromDreamApi(*loc); if (!srcValue.TryGetValueAsDreamObject(out var srcObj)) return SetLastError("loc was not a DreamObject"); if (srcObj == null) @@ -170,6 +172,8 @@ private static byte Byond_ReadVar(CByondValue* loc, byte* varname, CByondValue* var srcVar = srcObj.GetVariable(varName); var cSrcVar = ValueToByondApi(srcVar); *result = cSrcVar; + if (!calledFromMain) + AddTemporaryReference(srcVar); } catch (Exception e) { return SetLastError(e.Message); } @@ -193,13 +197,13 @@ private static byte Byond_ReadVarByStrId(CByondValue* loc, uint varname, CByondV if (loc == null || result == null) return SetLastError("loc or result argument was a null pointer"); - return RunOnMainThread(() => { + return RunOnMainThread(calledFromMain => { try { - DreamValue varNameVal = _refManager!.LocateRef((uint)RefType.String | varname); + using var varNameVal = _refManager!.LocateRef((uint)RefType.String | varname); if (!varNameVal.TryGetValueAsString(out var varName)) return SetLastError("varname argument was an invalid string ID"); - DreamValue srcValue = ValueFromDreamApi(*loc); + using var srcValue = ValueFromDreamApi(*loc); if (!srcValue.TryGetValueAsDreamObject(out var srcObj)) return SetLastError("loc argument was not a DreamObject"); if (srcObj == null) @@ -208,6 +212,8 @@ private static byte Byond_ReadVarByStrId(CByondValue* loc, uint varname, CByondV var srcVar = srcObj.GetVariable(varName); var cSrcVar = ValueToByondApi(srcVar); *result = cSrcVar; + if (!calledFromMain) + AddTemporaryReference(srcVar); } catch (Exception e) { return SetLastError(e.Message); } @@ -229,14 +235,14 @@ private static byte Byond_WriteVar(CByondValue* loc, byte* varname, CByondValue* if (loc == null || varname == null || val == null) return SetLastError("loc, varname, or val argument was a null pointer"); - return RunOnMainThread(() => { + return RunOnMainThread(_ => { try { string? varName = Marshal.PtrToStringUTF8((nint)varname); if (varName == null) return SetLastError("varname was a null pointer"); - DreamValue srcValue = ValueFromDreamApi(*val); - DreamValue dstValue = ValueFromDreamApi(*loc); + using var srcValue = ValueFromDreamApi(*val); + using var dstValue = ValueFromDreamApi(*loc); if (!dstValue.TryGetValueAsDreamObject(out var dstObj)) return SetLastError("loc argument was not a DreamObject"); if (dstObj == null) @@ -262,14 +268,14 @@ private static byte Byond_WriteVar(CByondValue* loc, byte* varname, CByondValue* */ [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] private static byte Byond_WriteVarByStrId(CByondValue* loc, uint varname, CByondValue* val) { - return RunOnMainThread(() => { + return RunOnMainThread(_ => { try { - DreamValue varNameVal = _refManager!.LocateRef((uint)RefType.String | varname); + using var varNameVal = _refManager!.LocateRef((uint)RefType.String | varname); if (!varNameVal.TryGetValueAsString(out var varName)) return SetLastError("varname argument was an invalid string ID"); - DreamValue srcValue = ValueFromDreamApi(*val); - DreamValue dstValue = ValueFromDreamApi(*loc); + using var srcValue = ValueFromDreamApi(*val); + using var dstValue = ValueFromDreamApi(*loc); if (!dstValue.TryGetValueAsDreamObject(out var dstObj)) return SetLastError("loc argument was not a DreamObject"); if (dstObj == null) @@ -292,8 +298,9 @@ private static byte Byond_WriteVarByStrId(CByondValue* loc, uint varname, CByond */ [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] private static byte Byond_CreateList(CByondValue* result) { - return RunOnMainThread(() => { + return RunOnMainThread(calledFromMain => { var newList = _objectTree!.CreateList(); + DreamValue val = new DreamValue(newList); try { *result = ValueToByondApi(val); @@ -301,6 +308,8 @@ private static byte Byond_CreateList(CByondValue* result) { return SetLastError(e.Message); } + if (!calledFromMain) + AddTemporaryReference(val); return 1; }); } @@ -320,8 +329,8 @@ private static byte Byond_ReadList(CByondValue* loc, CByondValue* list, uint* le int providedBufLen = (int)*len; - return RunOnMainThread(() => { - DreamValue srcValue = ValueFromDreamApi(*loc); + return RunOnMainThread(calledFromMain => { + using var srcValue = ValueFromDreamApi(*loc); if (!srcValue.TryGetValueAsDreamList(out var srcList)) { *len = 0; return SetLastError("loc argument was not a list"); @@ -341,6 +350,8 @@ private static byte Byond_ReadList(CByondValue* loc, CByondValue* list, uint* le throw new Exception($"List {srcList} had more elements than the expected {length}"); list[i++] = ValueToByondApi(value); + if (!calledFromMain) + AddTemporaryReference(value); } } catch (Exception e) { return SetLastError(e.Message); @@ -363,15 +374,15 @@ private static byte Byond_WriteList(CByondValue* loc, CByondValue* list, uint le if (list == null || loc == null) return SetLastError("list or loc argument was a null pointer"); - return RunOnMainThread(() => { + return RunOnMainThread(_ => { try { - DreamValue dstValue = ValueFromDreamApi(*loc); + using var dstValue = ValueFromDreamApi(*loc); if (!dstValue.TryGetValueAsDreamList(out DreamList? dstListValue)) return SetLastError("loc argument was not a list"); dstListValue.Cut(); for (int i = 0; i < len; i++) { - DreamValue srcValue = ValueFromDreamApi(list[i]); + using var srcValue = ValueFromDreamApi(list[i]); dstListValue.AddValue(srcValue); } } catch (Exception e) { @@ -397,8 +408,8 @@ private static byte Byond_ReadListAssoc(CByondValue* loc, CByondValue* list, uin int providedBufLen = (int)*len; - return RunOnMainThread(() => { - DreamValue srcValue = ValueFromDreamApi(*loc); + return RunOnMainThread(calledFromMain => { + using var srcValue = ValueFromDreamApi(*loc); if (!srcValue.TryGetValueAsDreamList(out var srcList)) { *len = 0; return SetLastError("loc argument was not a list"); @@ -418,6 +429,11 @@ private static byte Byond_ReadListAssoc(CByondValue* loc, CByondValue* list, uin list[i] = ValueToByondApi(entry.Key); list[i + 1] = ValueToByondApi(entry.Value); i += 2; + + if (!calledFromMain) { + AddTemporaryReference(entry.Key); + AddTemporaryReference(entry.Value); + } } } catch (Exception e) { return SetLastError(e.Message); @@ -440,15 +456,17 @@ private static byte Byond_ReadListIndex(CByondValue* loc, CByondValue* cIdx, CBy if (loc == null || cIdx == null || result == null) return SetLastError("loc, cIdx, or result argument was a null pointer"); - return RunOnMainThread(() => { + return RunOnMainThread(calledFromMain => { try { - DreamValue idx = ValueFromDreamApi(*cIdx); - DreamValue listValue = ValueFromDreamApi(*loc); + using var idx = ValueFromDreamApi(*cIdx); + using var listValue = ValueFromDreamApi(*loc); if (!listValue.TryGetValueAsDreamList(out var srcList)) return SetLastError("loc argument was not a list"); var val = srcList.GetValue(idx); *result = ValueToByondApi(val); + if (!calledFromMain) + AddTemporaryReference(val); } catch (Exception e) { return SetLastError(e.Message); } @@ -470,14 +488,14 @@ private static byte Byond_WriteListIndex(CByondValue* loc, CByondValue* cIdx, CB if (loc == null || cIdx == null || cVal == null) return SetLastError("loc, cIdx, or cVal argument was a null pointer"); - return RunOnMainThread(() => { + return RunOnMainThread(_ => { try { - DreamValue idx = ValueFromDreamApi(*cIdx); - DreamValue listValue = ValueFromDreamApi(*loc); + using var idx = ValueFromDreamApi(*cIdx); + using var listValue = ValueFromDreamApi(*loc); if (!listValue.TryGetValueAsDreamList(out var dstList)) return SetLastError("loc argument was not a list"); - var val = ValueFromDreamApi(*cVal); + using var val = ValueFromDreamApi(*cVal); dstList.SetValue(idx, val, true); } catch (Exception e) { return SetLastError(e.Message); @@ -517,19 +535,21 @@ private static byte Byond_WritePointer(CByondValue* cPtr, CByondValue* cVal) { throw new NotImplementedException(); } - private static byte CallProcShared(DreamObject? src, DreamProc proc, CByondValue* cArgs, uint arg_count, CByondValue* cResult) { + private static byte CallProcShared(DreamObject? src, DreamProc proc, CByondValue* cArgs, uint arg_count, CByondValue* cResult, bool tempRef) { DreamValue[] argList = new DreamValue[arg_count]; for (int i = 0; i < arg_count; i++) { - var arg = ValueFromDreamApi(cArgs[i]); + using var arg = ValueFromDreamApi(cArgs[i]); + argList[i] = arg; } var args = new DreamProcArguments(argList); - var result = proc.Spawn(src, args); *cResult = ValueToByondApi(result); + if (tempRef) + AddTemporaryReference(result); return 1; } @@ -550,13 +570,13 @@ private static byte Byond_CallProc(CByondValue* cSrc, byte* cName, CByondValue* if (cSrc == null || cArgs == null || cResult == null) return SetLastError("cSrc, cArgs, or cResult argument was a null pointer"); - return RunOnMainThread(() => { + return RunOnMainThread(calledFromMain => { try { string? str = Marshal.PtrToStringUTF8((nint)cName); if (str == null) return SetLastError("cName argument was a null pointer"); - DreamValue src = ValueFromDreamApi(*cSrc); + using var src = ValueFromDreamApi(*cSrc); if (!src.TryGetValueAsDreamObject(out var srcObj)) return SetLastError("cSrc argument was not a DreamObject"); if (srcObj == null) @@ -564,7 +584,7 @@ private static byte Byond_CallProc(CByondValue* cSrc, byte* cName, CByondValue* if (!srcObj.TryGetProc(str, out var proc)) return SetLastError($"cSrc argument does not own a proc named \"{str}\""); - return CallProcShared(srcObj, proc, cArgs, arg_count, cResult); + return CallProcShared(srcObj, proc, cArgs, arg_count, cResult, !calledFromMain); } catch (Exception e) { return SetLastError(e.Message); } @@ -588,13 +608,13 @@ private static byte Byond_CallProcByStrId(CByondValue* cSrc, uint name, CByondVa if (cSrc == null || cArgs == null || cResult == null) return SetLastError("cSrc, cArgs, or cResult argument was a null pointer"); - return RunOnMainThread(() => { + return RunOnMainThread(calledFromMain => { try { - DreamValue procNameVal = _refManager!.LocateRef((uint)RefType.String | name); + using var procNameVal = _refManager!.LocateRef((uint)RefType.String | name); if (!procNameVal.TryGetValueAsString(out var procName)) return SetLastError("name argument was an invalid string ID"); - DreamValue src = ValueFromDreamApi(*cSrc); + using var src = ValueFromDreamApi(*cSrc); if (!src.TryGetValueAsDreamObject(out var srcObj)) return SetLastError("cSrc argument was not a DreamObject"); if (srcObj == null) @@ -602,7 +622,7 @@ private static byte Byond_CallProcByStrId(CByondValue* cSrc, uint name, CByondVa if (!srcObj.TryGetProc(procName, out var proc)) return SetLastError($"cSrc does not own a proc named \"{procName}\""); - return CallProcShared(srcObj, proc, cArgs, arg_count, cResult); + return CallProcShared(srcObj, proc, cArgs, arg_count, cResult, !calledFromMain); } catch (Exception e) { return SetLastError(e.Message); } @@ -624,7 +644,7 @@ private static byte Byond_CallGlobalProc(byte* cName, CByondValue* cArgs, uint a if (cArgs == null || cResult == null) return SetLastError("cArgs or cResult argument was a null pointer"); - return RunOnMainThread(() => { + return RunOnMainThread(calledFromMain => { try { string? str = Marshal.PtrToStringUTF8((nint)cName); if (str == null) @@ -632,7 +652,7 @@ private static byte Byond_CallGlobalProc(byte* cName, CByondValue* cArgs, uint a if (!_dreamManager!.TryGetGlobalProc(str, out var proc)) return SetLastError($"no global proc named \"{str}\""); - CallProcShared(null, proc, cArgs, arg_count, cResult); + CallProcShared(null, proc, cArgs, arg_count, cResult, !calledFromMain); } catch (Exception e) { return SetLastError(e.Message); } @@ -657,15 +677,15 @@ private static byte Byond_CallGlobalProcByStrId(uint name, CByondValue* cArgs, u if (cArgs == null || cResult == null) return SetLastError("cArgs or cResult argument was a null pointer"); - return RunOnMainThread(() => { + return RunOnMainThread(calledFromMain => { try { - DreamValue procNameVal = _refManager!.LocateRef((uint)RefType.String | name); + using var procNameVal = _refManager!.LocateRef((uint)RefType.String | name); if (!procNameVal.TryGetValueAsString(out var procName)) return SetLastError("name argument was an invalid string ID"); if (!_dreamManager!.TryGetGlobalProc(procName, out var proc)) return SetLastError($"no global proc named \"{procName}\""); - CallProcShared(null, proc, cArgs, arg_count, cResult); + CallProcShared(null, proc, cArgs, arg_count, cResult, calledFromMain); } catch (Exception e) { return SetLastError(e.Message); } @@ -687,10 +707,10 @@ private static byte Byond_ToString(CByondValue* src, byte* buf, uint* buflen) { if (src == null || buflen == null) return SetLastError("src or buflen argument was a null pointer"); - return RunOnMainThread(() => { + return RunOnMainThread(_ => { try { int providedBufLen = (int)*buflen; - DreamValue srcValue = ValueFromDreamApi(*src); + using var srcValue = ValueFromDreamApi(*src); var str = srcValue.Stringify(); var utf8 = Encoding.UTF8.GetBytes(str); int length = utf8.Length; @@ -726,7 +746,7 @@ private static byte Byond_Block(CByondXYZ* corner1, CByondXYZ* corner2, CByondVa if (corner1 == null || corner2 == null || cList == null || len == null) return SetLastError("corner1, corner2, cList, or len argument was a null pointer"); - return RunOnMainThread(() => { + return RunOnMainThread(calledFromMain => { List list = new(); try { var turfs = DreamProcNativeRoot.Block(_objectTree!, _dreamMapManager!, @@ -735,6 +755,8 @@ private static byte Byond_Block(CByondXYZ* corner1, CByondXYZ* corner2, CByondVa foreach (var turf in turfs.EnumerateValues()) { list.Add(ValueToByondApi(turf)); + if (!calledFromMain) + AddTemporaryReference(turf); } } catch (Exception e) { return SetLastError(e.Message); @@ -766,14 +788,15 @@ private static byte Byond_Length(CByondValue* src, CByondValue* result) { if (src == null || result == null) return SetLastError("src or result argument was a null pointer"); - return RunOnMainThread(() => { - DreamValue srcValue = ValueFromDreamApi(*src); + return RunOnMainThread(_ => { + using var srcValue = ValueFromDreamApi(*src); try { *result = ValueToByondApi(DreamProcNativeRoot._length(srcValue, true)); - return 1; } catch (Exception e) { return SetLastError(e.Message); } + + return 1; }); } @@ -798,11 +821,13 @@ private static byte Byond_LocateXYZ(CByondXYZ* xyz, CByondValue* result) { return 1; } - return RunOnMainThread(() => { + return RunOnMainThread(calledFromMain => { try { if (_dreamMapManager!.TryGetTurfAt(new Vector2i(xyz->x, xyz->y), xyz->z, out var turf)) { DreamValue val = new(turf); *result = ValueToByondApi(val); + if (!calledFromMain) + AddTemporaryReference(val); } else { *result = ValueToByondApi(DreamValue.Null); } @@ -829,9 +854,9 @@ private static byte Byond_New(CByondValue* cType, CByondValue* cArgs, uint arg_c if (cType == null || cArgs == null || cResult == null) return SetLastError("cType, cArgs, or cResult argument was a null pointer"); - return RunOnMainThread(() => { + return RunOnMainThread(calledFromMain => { try { - var typeVal = ValueFromDreamApi(*cType); + using var typeVal = ValueFromDreamApi(*cType); if (!typeVal.TryGetValueAsType(out var treeEntry)) { if (typeVal.TryGetValueAsString(out var pathString)) { if (!_objectTree!.TryGetTreeEntry(pathString, out treeEntry)) @@ -844,7 +869,8 @@ private static byte Byond_New(CByondValue* cType, CByondValue* cArgs, uint arg_c var objectDef = treeEntry.ObjectDefinition; var argList = new DreamValue[arg_count]; for (int i = 0; i < arg_count; i++) { - var arg = ValueFromDreamApi(cArgs[i]); + using var arg = ValueFromDreamApi(cArgs[i]); + argList[i] = arg; } @@ -865,7 +891,11 @@ private static byte Byond_New(CByondValue* cType, CByondValue* cArgs, uint arg_c var newObject = _objectTree.CreateObject(treeEntry); newObject.InitSpawn(args); - *cResult = ValueToByondApi(new DreamValue(newObject)); + + var newObjectValue = new DreamValue(newObject); + *cResult = ValueToByondApi(newObjectValue); + if (!calledFromMain) + AddTemporaryReference(newObjectValue); } catch (Exception e) { return SetLastError(e.Message); } @@ -888,9 +918,9 @@ private static byte Byond_NewArglist(CByondValue* cType, CByondValue* cArglist, if (cType == null || cArglist == null || cResult == null) return SetLastError("cType, cArglist, or cResult argument was a null pointer"); - return RunOnMainThread(() => { + return RunOnMainThread(calledFromMain => { try { - var typeVal = ValueFromDreamApi(*cType); + using var typeVal = ValueFromDreamApi(*cType); if (!typeVal.TryGetValueAsType(out var treeEntry)) { if (typeVal.TryGetValueAsString(out var pathString)) { if (!_objectTree!.TryGetTreeEntry(pathString, out treeEntry)) @@ -902,7 +932,7 @@ private static byte Byond_NewArglist(CByondValue* cType, CByondValue* cArglist, var objectDef = treeEntry.ObjectDefinition; - var arglistVal = ValueFromDreamApi(*cArglist); + using var arglistVal = ValueFromDreamApi(*cArglist); if (!arglistVal.TryGetValueAsIDreamList(out var arglist)) return SetLastError("cArglist argument was not a list"); @@ -925,7 +955,11 @@ private static byte Byond_NewArglist(CByondValue* cType, CByondValue* cArglist, var newObject = _objectTree.CreateObject(treeEntry); newObject.InitSpawn(args); - *cResult = ValueToByondApi(new DreamValue(newObject)); + + var newObjectValue = new DreamValue(newObject); + *cResult = ValueToByondApi(newObjectValue); + if (!calledFromMain) + AddTemporaryReference(newObjectValue); } catch (Exception e) { return SetLastError(e.Message); } @@ -946,14 +980,12 @@ private static byte Byond_Refcount(CByondValue* src, uint* result) { if (src == null || result == null) return SetLastError("src or result argument was a null pointer"); - return RunOnMainThread(() => { - // TODO - // woah that's a lot of refs - // i wonder if it's true?? - *result = 100; - // (it's not) + return RunOnMainThread(_ => { + using var value = ValueFromDreamApi(*src); + if (!value.TryGetValueAsDreamObject(out var dreamObject)) + return 0; - return 1; + return (byte)(dreamObject.RefCount - 1); // Don't count the active reference held by this function }); } @@ -971,9 +1003,9 @@ private static byte Byond_XYZ(CByondValue* src, CByondXYZ* xyz) { *xyz = new CByondXYZ(); - return RunOnMainThread(() => { + return RunOnMainThread(_ => { try { - var srcVal = ValueFromDreamApi(*src); + using var srcVal = ValueFromDreamApi(*src); if (!srcVal.TryGetValueAsDreamObject(out var srcObj)) return SetLastError("src argument was not an atom"); @@ -1028,8 +1060,16 @@ private static byte Byond_BoundPixLoc(CByondValue* src, byte dir, CByondPixLoc* */ [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] private static void ByondValue_IncRef(CByondValue* src) { - //if (src == null) return; - //throw new NotImplementedException(); + if (src == null) + return; + + RunOnMainThread(_ => { + using var srcValue = ValueFromDreamApi(*src); + if (srcValue.TryGetValueAsDreamObject(out var dreamObject)) + dreamObject.IncRef(); + + return 0; + }); } /** byondapi.h comment: @@ -1041,8 +1081,16 @@ private static void ByondValue_IncRef(CByondValue* src) { */ [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] private static void ByondValue_DecRef(CByondValue* src) { - //if (src == null) return; - //throw new NotImplementedException(); + if (src == null) + return; + + RunOnMainThread(_ => { + using var srcValue = ValueFromDreamApi(*src); + if (srcValue.TryGetValueAsDreamObject(out var dreamObject)) + dreamObject.DecRef(); + + return 0; + }); } /** byondapi.h comment: @@ -1053,8 +1101,12 @@ private static void ByondValue_DecRef(CByondValue* src) { */ [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] private static void ByondValue_DecTempRef(CByondValue* src) { - //if (src == null) return; - //throw new NotImplementedException(); + //if (src == null || !OnMainThread()) + // return; + + // TODO + // Maybe not worth doing? + // Newer versions get rid of this, and calling it doesn't result in much different behavior } /** byondapi.h comment: @@ -1073,8 +1125,8 @@ private static byte Byond_TestRef(CByondValue* src) { if (src->type == ByondValueType.Null) return 1; - return RunOnMainThread(() => { - var srcValue = ValueFromDreamApi(*src); + return RunOnMainThread(_ => { + using var srcValue = ValueFromDreamApi(*src); if (srcValue == DreamValue.Null) { src->type = 0; diff --git a/OpenDreamRuntime/ByondApi/ByondApi.cs b/OpenDreamRuntime/ByondApi/ByondApi.cs index 30594a999d..c32f92888a 100644 --- a/OpenDreamRuntime/ByondApi/ByondApi.cs +++ b/OpenDreamRuntime/ByondApi/ByondApi.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Threading.Tasks; +using JetBrains.Annotations; using OpenDreamRuntime.Map; using OpenDreamRuntime.Objects; using Robust.Shared.Utility; @@ -18,6 +19,7 @@ public static partial class ByondApi { private static DreamObjectTree? _objectTree; private static readonly ConcurrentQueue ThreadSyncQueue = new(); + private static readonly Queue TemporaryReferences = new(); private static int _mainThreadId; /// @@ -48,15 +50,21 @@ public static void Shutdown() { Marshal.FreeHGlobal(_lastErrorPtr); } - public static void ExecuteThreadSyncs() { + /// + /// Execute thread syncs and remove temporary references + /// + public static void Update() { while (ThreadSyncQueue.TryDequeue(out var task)) task.Invoke(); + while (TemporaryReferences.TryDequeue(out var tempRef)) + tempRef.DecRef(); } /// /// Converts a CByondValue to a DreamValue /// /// Must be run on the main thread + [MustDisposeResource] public static DreamValue ValueFromDreamApi(CByondValue value) { DebugTools.AssertEqual(Environment.CurrentManagedThreadId, _mainThreadId); @@ -150,26 +158,32 @@ private static byte SetLastError(string lastError) { return 0; } + private static bool OnMainThread() => Environment.CurrentManagedThreadId == _mainThreadId; + [SuppressMessage("Usage", "RA0004:Risk of deadlock from accessing Task.Result")] - private static T RunOnMainThread(Func task) { - if (Environment.CurrentManagedThreadId == _mainThreadId) - return task.Invoke(); + private static T RunOnMainThread(Func task) { + if (OnMainThread()) + return task.Invoke(true); var tcs = new TaskCompletionSource(); ThreadSyncQueue.Enqueue(() => { - tcs.SetResult(task.Invoke()); + tcs.SetResult(task.Invoke(false)); }); return tcs.Task.Result; } private static void RunOnMainThreadNonBlocking(Action task) { - if (Environment.CurrentManagedThreadId == _mainThreadId) { + if (OnMainThread()) { task(); return; } ThreadSyncQueue.Enqueue(task); } + + private static void AddTemporaryReference([HandlesResourceDisposal] DreamValue value) { + TemporaryReferences.Enqueue(value); + } } diff --git a/OpenDreamRuntime/DreamConnection.cs b/OpenDreamRuntime/DreamConnection.cs index 6b1e7c7638..fc5cf90ed5 100644 --- a/OpenDreamRuntime/DreamConnection.cs +++ b/OpenDreamRuntime/DreamConnection.cs @@ -32,18 +32,20 @@ public sealed class DreamConnection { [ViewVariables] public ICommonSession? Session { get; private set; } [ViewVariables] public DreamObjectClient? Client { get; private set; } - [ViewVariables] public string Key { get; private set; } + [ViewVariables] public string Key { get; } [ViewVariables] public DreamObjectMob? Mob { get => _mob; set { if (_mob != value) { var oldMob = _mob; + value?.IncRef(); + _mob?.DecRef(); _mob = value; if (oldMob != null) { oldMob.Key = null; - oldMob.SpawnProc("Logout"); + oldMob.SpawnProc("Logout").Dispose(); oldMob.Connection = null; } @@ -54,12 +56,11 @@ [ViewVariables] public DreamObjectMob? Mob { if (_mob != null) { // If the mob is already owned by another player, kick them out - if (_mob.Connection != null) - _mob.Connection.Mob = null; + _mob.Connection?.Mob = null; _mob.Connection = this; _mob.Key = Key; - _mob.SpawnProc("Login", usr: _mob); + _mob.SpawnProc("Login", usr: _mob).Dispose(); } } } @@ -68,6 +69,8 @@ [ViewVariables] public DreamObjectMob? Mob { [ViewVariables] public DreamObjectMovable? Eye { get; set { + value?.IncRef(); + field?.DecRef(); field = value; if (Session != null) _playerManager.SetAttachedEntity(Session, field?.Entity); @@ -124,7 +127,7 @@ public void HandleDisconnection() { if (_mob != null) { // Don't null out the ckey here - _mob.SpawnProc("Logout"); + _mob.SpawnProc("Logout").Dispose(); if (_mob != null) { // Logout() may have removed our mob _mob.Connection = null; @@ -132,6 +135,7 @@ public void HandleDisconnection() { } } + Client.DecRef(); Client.Delete(); Client = null; } @@ -157,7 +161,7 @@ public void UpdateStat() { } finally { _currentlyUpdatingStat = false; } - }); + }).Dispose(); } public void SendClientInfoUpdate() { @@ -222,6 +226,7 @@ public void HandleMsgSoundQueryResponse(MsgSoundQueryResponse message) { sound.SetVariableValue("file", string.IsNullOrEmpty(soundData.File) ? DreamValue.Null : new DreamValue(soundData.File)); allSounds.AddValue(new DreamValue(sound)); + sound.DecRef(); } promptEvent.Invoke(new DreamValue(allSounds)); @@ -229,34 +234,31 @@ public void HandleMsgSoundQueryResponse(MsgSoundQueryResponse message) { } public void HandleMsgTopic(MsgTopic pTopic) { - DreamList hrefList = DreamProcNativeRoot.Params2List(_objectTree, HttpUtility.UrlDecode(pTopic.Query)); - DreamValue srcRefValue = hrefList.GetValue(new DreamValue("src")); - DreamValue src = DreamValue.Null; + var hrefList = DreamProcNativeRoot.Params2List(_objectTree, HttpUtility.UrlDecode(pTopic.Query)); + using var srcRefValue = hrefList.GetValue(new DreamValue("src")); + var src = DreamValue.Null; if (srcRefValue.TryGetValueAsString(out var srcRef)) { src = _refManager.LocateRef(srcRef); } - Client?.SpawnProc("Topic", usr: Mob, new(pTopic.Query), new(hrefList), src); + Client?.SpawnProc("Topic", usr: Mob, new(pTopic.Query), new(hrefList), src).Dispose(); + src.Dispose(); + hrefList.DecRef(); } public void OutputDreamValue(DreamValue value) { - if (value.TryGetValueAsDreamObject(out var outputObject)) { - ushort channel = (ushort)outputObject.GetVariable("channel").GetValueAsInteger(); - ushort volume = (ushort)outputObject.GetVariable("volume").GetValueAsInteger(); - float offset = outputObject.GetVariable("offset").UnsafeGetValueAsFloat(); - byte repeat = (byte)Math.Clamp(outputObject.GetVariable("repeat").UnsafeGetValueAsFloat(), 0, 2); - DreamValue file = outputObject.GetVariable("file"); - + if (value.TryGetValueAsDreamObject(out var sound)) { var msg = new MsgSound { SoundData = new SoundData { - Channel = channel, - Volume = volume, - Offset = offset, - Repeat = repeat + Channel = sound.Channel, + Volume = sound.Volume, + Offset = sound.Offset, + Repeat = sound.Repeat } }; + var file = sound.File; if (!file.TryGetValueAsDreamResource(out var soundResource)) { if (file.TryGetValueAsString(out var soundPath)) { soundResource = _resourceManager.LoadResource(soundPath); @@ -324,6 +326,8 @@ public Task SoundQuery() { public async Task PromptList(DreamValueType types, IDreamList list, string title, string message, DreamValue defaultValue) { DreamValue[] listValues = list.CopyToArray(); + foreach (var value in listValues) + value.IncRef(); List promptValues = new(listValues.Length); foreach (var value in listValues) { @@ -357,12 +361,26 @@ public async Task PromptList(DreamValueType types, IDreamList list, // The client returns the index of the selected item, this needs turned back into the DreamValue. var selectedIndex = await task; if (selectedIndex.TryGetValueAsInteger(out int index) && index < listValues.Length) { - return listValues[index]; + var selected = listValues[index]; + + selected.IncRef(); + foreach (var value in listValues) + value.DecRef(); + + return selected; } // Client returned an invalid value. // Return the first value in the list, or null if cancellable - return msg.CanCancel ? DreamValue.Null : listValues[0]; + if (msg.CanCancel) { + return DreamValue.Null; + } else { + listValues[0].IncRef(); + foreach (var value in listValues) + value.DecRef(); + + return listValues[0]; + } } public Task WinExists(string controlId) { diff --git a/OpenDreamRuntime/DreamManager.Connections.cs b/OpenDreamRuntime/DreamManager.Connections.cs index 9778fda616..d211140d39 100644 --- a/OpenDreamRuntime/DreamManager.Connections.cs +++ b/OpenDreamRuntime/DreamManager.Connections.cs @@ -146,7 +146,7 @@ private async Task ConsumeAndHandleWorldTopicSocket(Socket remote, CancellationT var result = await state.Call(topicProc, WorldInstance, null, new DreamValue(topic), new DreamValue(remoteAddress)); tcs.SetResult(result); return result; - }); + }).Dispose(); var topicResponse = await tcs.Task; if (topicResponse.IsNull) { diff --git a/OpenDreamRuntime/DreamManager.cs b/OpenDreamRuntime/DreamManager.cs index 2eb1bf7b5b..d3d00f2450 100644 --- a/OpenDreamRuntime/DreamManager.cs +++ b/OpenDreamRuntime/DreamManager.cs @@ -1,5 +1,4 @@ -using System.Collections.Concurrent; -using System.IO; +using System.IO; using System.Text.Json; using DMCompiler.Bytecode; using DMCompiler.Json; @@ -31,8 +30,6 @@ public sealed partial class DreamManager { public List GlobalNames { get; private set; } = new(); public HashSet Clients { get; } = new(); - public readonly ConcurrentBag DelQueue = new(); - public readonly HashSet RefDeleteQueue = new(); public Random Random { get; set; } = new(); public DreamProc ImageConstructor, ImageFactoryProc; public int ListPoolThreshold, ListPoolSize; @@ -83,20 +80,22 @@ public void StartWorld() { CurrentTickStart = Environment.TickCount64; // Call global with waitfor=FALSE - _objectTree.GlobalInitProc?.Spawn(WorldInstance, new()); + _objectTree.GlobalInitProc?.Spawn(WorldInstance, new()).Dispose(); // Call New() on all /area and /turf that exist, each with waitfor=FALSE separately. If created any /area, call New a SECOND TIME // new() up /objs and /mobs from compiled-in maps [order: (1,1) then (2,1) then (1,2) then (2,2)] _dreamMapManager.InitializeAtoms(); // Call world.New() - WorldInstance.SpawnProc("New"); + WorldInstance.SpawnProc("New").Dispose(); } } public void Shutdown() { - // TODO: Respect not calling parent and aborting shutdown + WorldInstance.DecRef(); WorldInstance.Delete(); + WorldInstance = null!; + ShutdownConnectionManager(); Initialized = false; IsShutDown = true; @@ -117,16 +116,13 @@ public void Update() { _dreamMapManager.UpdateTiles(); } - using (Profiler.BeginZone("ByondApi Thread Syncs")) - ByondApi.ByondApi.ExecuteThreadSyncs(); + using (Profiler.BeginZone("ByondApi Thread Syncs & Temp Refs")) + ByondApi.ByondApi.Update(); using (Profiler.BeginZone("Disk IO", color: (uint)Color.LightPink.ToArgb())) DreamObjectSavefile.FlushAllUpdates(); WorldInstance.Cpu = WorldInstance.TickUsage; - - using (Profiler.BeginZone("Deletion Queue", color: (uint)Color.LightPink.ToArgb())) - ProcessDelQueue(); } Profiler.EmitFrameMark(); @@ -155,8 +151,6 @@ public bool LoadJson(string? jsonPath) { var resources = json.Resources ?? Array.Empty(); _dreamResourceManager.Initialize(rootPath, resources, json.Interface); - DelQueue.Clear(); - RefDeleteQueue.Clear(); _refManager.Initialize(); _objectTree.LoadJson(json); DreamProcNative.SetupNativeProcs(_objectTree); @@ -174,8 +168,10 @@ public bool LoadJson(string? jsonPath) { GlobalNames = jsonGlobals.Names; for (int i = 0; i < jsonGlobals.GlobalCount; i++) { - object globalValue = jsonGlobals.Globals.GetValueOrDefault(i, null); - Globals[i] = _objectTree.GetDreamValueFromJsonElement(globalValue); + var globalJson = jsonGlobals.Globals.GetValueOrDefault(i, null); + using var globalValue = _objectTree.GetDreamValueFromJsonElement(globalJson); + + SetGlobal(i, globalValue); } } @@ -184,7 +180,8 @@ public bool LoadJson(string? jsonPath) { } public void WriteWorldLog(string message, LogLevel level = LogLevel.Info, string sawmill = "world.log") { - if (!WorldInstance.GetVariable("log").TryGetValueAsDreamResource(out var logRsc)) { + using var worldLog = WorldInstance.GetVariable("log"); + if (!worldLog.TryGetValueAsDreamResource(out var logRsc)) { logRsc = new ConsoleOutputResource(); WorldInstance.SetVariableValue("log", new DreamValue(logRsc)); _sawmill.Log(LogLevel.Error, $"Failed to write to the world log, falling back to console output. Original log message follows: [{LogMessage.LogLevelToName(level)}] world.log: {message}"); @@ -244,11 +241,13 @@ public void HandleException(Exception e, string msg = "", string file = "", int obj.Line = new DreamValue(line); obj.File = new DreamValue(file); if (!inWorldError) // if an error occurs in /world/Error(), don't call it again - WorldInstance.SpawnProc("Error", usr: null, new DreamValue(obj)); + WorldInstance.SpawnProc("Error", usr: null, new DreamValue(obj)).Dispose(); else { _sawmill.Error("CRITICAL: An error occurred in /world/Error()"); WriteWorldLog(msg); } + + obj.DecRef(); } public void OptionalException(WarningCode code, string exceptionText) where T : Exception { @@ -258,17 +257,9 @@ public void OptionalException(WarningCode code, string exceptionText) where T } } - private void ProcessDelQueue() { - lock (RefDeleteQueue) { - foreach (var refId in RefDeleteQueue) { - _refManager.DeleteRef(refId); - } - - RefDeleteQueue.Clear(); - } - - while (DelQueue.TryTake(out var obj)) { - obj.Delete(); - } + public void SetGlobal(int id, DreamValue value) { + value.IncRef(); + Globals[id].Dispose(); + Globals[id] = value; } } diff --git a/OpenDreamRuntime/DreamThread.cs b/OpenDreamRuntime/DreamThread.cs index 094d394944..e77fdf9465 100644 --- a/OpenDreamRuntime/DreamThread.cs +++ b/OpenDreamRuntime/DreamThread.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using DMCompiler.DM; +using JetBrains.Annotations; using OpenDreamRuntime.Objects; using OpenDreamRuntime.Procs; using OpenDreamRuntime.Procs.DebugAdapter; @@ -64,11 +65,12 @@ protected DreamProc(int id, TreeEntry owningType, string name, DreamProc? superP Invisibility = invisibility; } - public abstract ProcState CreateState(DreamThread thread, DreamObject? src, DreamObject? usr, DreamProcArguments arguments); + public abstract ProcState CreateState(DreamThread thread, DreamObject? src, DreamObject? usr, [HandlesResourceDisposal] DreamProcArguments arguments); // Execute this proc. This will behave as if the proc has `set waitfor = 0` - public DreamValue Spawn(DreamObject src, DreamProcArguments arguments, DreamObject? usr = null) { - var context = new DreamThread(this.ToString()); + [MustDisposeResource] + public DreamValue Spawn(DreamObject src, [HandlesResourceDisposal] DreamProcArguments arguments, DreamObject? usr = null) { + var context = new DreamThread(ToString()); var state = CreateState(context, src, usr, arguments); context.PushProcState(state); return context.Resume(); @@ -101,8 +103,13 @@ public override string ToString() { } [Virtual] - internal class DMThrowException(DreamValue value) : Exception(GetRuntimeMessage(value)) { - public readonly DreamValue Value = value; + internal class DMThrowException : Exception { + public readonly DreamValue Value; + + public DMThrowException(DreamValue value) : base(GetRuntimeMessage(value)) { + Value = value; + Value.IncRef(); // Better hope we're gracefully caught! + } private static string GetRuntimeMessage(DreamValue value) { string? name; @@ -110,6 +117,7 @@ private static string GetRuntimeMessage(DreamValue value) { value.TryGetValueAsDreamObject(out var dreamObject); if (dreamObject?.TryGetVariable("name", out var nameVar) == true) { name = nameVar.TryGetValueAsString(out name) ? name : string.Empty; + nameVar.Dispose(); } else { name = string.Empty; } @@ -130,6 +138,7 @@ public abstract class ProcState : IDisposable { public int Id { get; private set; } public DreamThread Thread { get; set; } = default!; + #if TOOLS public abstract (string SourceFile, int Line) TracyLocationId { get; } public ProfilerZone? TracyZoneId { get; set; } @@ -174,6 +183,7 @@ public virtual void Cancel() {} public virtual void Dispose() { Thread = null!; + Result.DecRef(); Result = DreamValue.Null; WaitFor = true; Id = -1; @@ -208,6 +218,7 @@ public sealed class DreamThread(string name) { internal DreamDebugManager.ThreadStepMode? StepMode { get; set; } + [MustDisposeResource] public static DreamValue Run(DreamProc proc, DreamObject src, DreamObject? usr, params DreamValue[] arguments) { var context = new DreamThread(proc.ToString()); @@ -223,13 +234,16 @@ public static DreamValue Run(DreamProc proc, DreamObject src, DreamObject? usr, return context.Resume(); } + [MustDisposeResource] public static DreamValue Run(string name, Func> anonymousFunc) { var context = new DreamThread(name); var state = AsyncNativeProc.CreateAnonymousState(context, anonymousFunc); + context.PushProcState(state); return context.Resume(); } + [MustDisposeResource] public DreamValue Resume() { return ReentrantResume(null, out _); } @@ -242,6 +256,7 @@ public DreamValue Resume() { /// This function is suitable for executing from inside a running opcode handler if /// is provided. /// + /// Ownership of the return value will be transferred to the caller, so be sure to call . /// /// /// If not null, only continue running until this proc state gets returned into. @@ -251,6 +266,7 @@ public DreamValue Resume() { /// The proc result status that caused this resume to return. /// /// The return value of the last proc to return. + [MustDisposeResource] public DreamValue ReentrantResume(ProcState? untilState, out ProcStatus resultStatus) { try { CurrentlyExecuting.Value!.Push(this); @@ -314,6 +330,7 @@ public DreamValue ReentrantResume(ProcState? untilState, out ProcStatus resultSt // Our top-most proc just returned a value case ProcStatus.Returned: var returned = _current.Result; + returned.IncRef(); PopProcState(); // If our stack is empty, the context has finished execution @@ -325,6 +342,7 @@ public DreamValue ReentrantResume(ProcState? untilState, out ProcStatus resultSt // ... otherwise we just push the return value onto the dm caller's stack _current.ReturnedInto(returned); + returned.DecRef(); break; // The context is done executing for now diff --git a/OpenDreamRuntime/DreamValue.cs b/OpenDreamRuntime/DreamValue.cs index 2c4deecd50..389866402c 100644 --- a/OpenDreamRuntime/DreamValue.cs +++ b/OpenDreamRuntime/DreamValue.cs @@ -20,7 +20,7 @@ namespace OpenDreamRuntime; [JsonConverter(typeof(DreamValueJsonConverter))] -public struct DreamValue : IEquatable { +public struct DreamValue : IDisposable, IEquatable { public enum DreamValueType { // @formatter:off String = 1, @@ -139,6 +139,24 @@ public readonly override string ToString() { } } + public void Dispose() { + DecRef(); + } + + public void IncRef() { + if (Type != DreamValueType.DreamObject || _refValue == null) + return; + + Unsafe.As(_refValue).IncRef(); + } + + public void DecRef() { + if (Type != DreamValueType.DreamObject || _refValue == null) + return; + + Unsafe.As(_refValue).DecRef(); + } + [Obsolete("Deprecated. Use TryGetValueAsString() or MustGetValueAsString() instead.")] public string GetValueAsString() { return MustGetValueAsString(); @@ -694,13 +712,10 @@ public Matrix3x2 Read(ISerializationManager serializationManager, throw new Exception($"Value {node.Value} was not a matrix"); // Matrix3 except not really because DM matrix is actually 3x2 - matrixObject.GetVariable("a").TryGetValueAsFloat(out var a); - matrixObject.GetVariable("b").TryGetValueAsFloat(out var b); - matrixObject.GetVariable("c").TryGetValueAsFloat(out var c); - matrixObject.GetVariable("d").TryGetValueAsFloat(out var d); - matrixObject.GetVariable("e").TryGetValueAsFloat(out var e); - matrixObject.GetVariable("f").TryGetValueAsFloat(out var f); - return new Matrix3x2(a, d, b, e, c, f); + return new Matrix3x2( + matrixObject.A, matrixObject.D, + matrixObject.B, matrixObject.E, + matrixObject.C, matrixObject.F); } public ValidationNode Validate(ISerializationManager serializationManager, diff --git a/OpenDreamRuntime/EntryPoint.cs b/OpenDreamRuntime/EntryPoint.cs index fb78d14407..b6f390f60d 100644 --- a/OpenDreamRuntime/EntryPoint.cs +++ b/OpenDreamRuntime/EntryPoint.cs @@ -1,5 +1,4 @@ -using OpenDreamRuntime.Input; -using OpenDreamRuntime.Objects.Types; +using OpenDreamRuntime.Objects.Types; using OpenDreamRuntime.Procs.DebugAdapter; using OpenDreamShared; using Robust.Shared; @@ -44,7 +43,7 @@ public override void Init() { _configManager.OverrideDefault(CVars.GameAutoPauseEmpty, false); // DreamObjectWorld sets this appropriately but we need to keep it disabled til then or it won't be reached _configManager.OverrideDefault(CVars.DiscordRichPresenceSecondIconId, "opendream"); _configManager.SetCVar(CVars.GridSplitting, false); // Grid splitting should never be used - if(String.IsNullOrEmpty(_configManager.GetCVar(OpenDreamCVars.JsonPath))) //if you haven't set the jsonpath cvar, set it to the first valid file path passed as an arg + if(string.IsNullOrEmpty(_configManager.GetCVar(OpenDreamCVars.JsonPath))) //if you haven't set the jsonpath cvar, set it to the first valid file path passed as an arg foreach (string arg in Environment.GetCommandLineArgs().Skip(1)) //skip the first element, because it's just the server's exe path if(File.Exists(arg)){ _configManager.SetCVar(OpenDreamCVars.JsonPath, arg); @@ -64,7 +63,7 @@ public override void PostInit() { int debugAdapterPort = _configManager.GetCVar(OpenDreamCVars.DebugAdapterLaunched); if (debugAdapterPort == 0) { - _dreamManager.PreInitialize(_configManager.GetCVar(OpenDreamCVars.JsonPath)); + _dreamManager.PreInitialize(_configManager.GetCVar(OpenDreamCVars.JsonPath)); _dreamManager.StartWorld(); } else { // The debug manager is responsible for running _dreamManager.PreInitialize() and .StartWorld() diff --git a/OpenDreamRuntime/Input/MouseInputSystem.cs b/OpenDreamRuntime/Input/MouseInputSystem.cs index 56967faa32..57c4201e41 100644 --- a/OpenDreamRuntime/Input/MouseInputSystem.cs +++ b/OpenDreamRuntime/Input/MouseInputSystem.cs @@ -66,11 +66,12 @@ private void OnAtomDragged(AtomDraggedEvent e, EntitySessionEventArgs sessionEve overLocValue, DreamValue.Null, // TODO: src_control and over_control DreamValue.Null, - new DreamValue(ConstructClickParams(e.Params))); + new DreamValue(ConstructClickParams(e.Params))).Dispose(); } private void OnStatClicked(StatClickedEvent e, EntitySessionEventArgs sessionEvent) { - if (!_refManager.LocateRef(e.AtomRef).TryGetValueAsDreamObject(out var dreamObject)) + using var atom = _refManager.LocateRef(e.AtomRef); + if (!atom.TryGetValueAsDreamObject(out var dreamObject)) return; HandleAtomClick(e, dreamObject, sessionEvent); @@ -84,10 +85,11 @@ private void OnMouseEntered(MouseEnteredEvent e, EntitySessionEventArgs sessionE if (!_atomManager.GetEnabledMouseEvents(atom).HasFlag(AtomMouseEvents.Enter)) return; + using var loc = atom.GetVariable("loc"); atom.SpawnProc("MouseEntered", usr: connection.Mob, - atom.GetVariable("loc"), + loc, DreamValue.Null, - new DreamValue(ConstructClickParams(e.Params))); + new DreamValue(ConstructClickParams(e.Params))).Dispose(); } private void OnMouseExited(MouseExitedEvent e, EntitySessionEventArgs sessionEvent) { @@ -98,10 +100,11 @@ private void OnMouseExited(MouseExitedEvent e, EntitySessionEventArgs sessionEve if (!_atomManager.GetEnabledMouseEvents(atom).HasFlag(AtomMouseEvents.Exit)) return; + using var loc = atom.GetVariable("loc"); atom.SpawnProc("MouseExited", usr: connection.Mob, - atom.GetVariable("loc"), + loc, DreamValue.Null, - new DreamValue(ConstructClickParams(e.Params))); + new DreamValue(ConstructClickParams(e.Params))).Dispose(); } private void OnMouseMove(MouseMoveEvent e, EntitySessionEventArgs sessionEvent) { @@ -112,10 +115,11 @@ private void OnMouseMove(MouseMoveEvent e, EntitySessionEventArgs sessionEvent) if (!_atomManager.GetEnabledMouseEvents(atom).HasFlag(AtomMouseEvents.Move)) return; + using var loc = atom.GetVariable("loc"); atom.SpawnProc("MouseMove", usr: connection.Mob, - atom.GetVariable("loc"), + loc, DreamValue.Null, - new DreamValue(ConstructClickParams(e.Params))); + new DreamValue(ConstructClickParams(e.Params))).Dispose(); } private void HandleAtomClick(IAtomMouseEvent e, DreamObjectAtom atom, EntitySessionEventArgs sessionEvent) { @@ -131,14 +135,14 @@ private void HandleAtomClick(IAtomMouseEvent e, DreamObjectAtom atom, EntitySess new DreamValue(atom), DreamValue.Null, DreamValue.Null, - new DreamValue(clickParams)); + new DreamValue(clickParams)).Dispose(); } connection.Client?.SpawnProc("Click", usr: usr, new DreamValue(atom), DreamValue.Null, DreamValue.Null, - new DreamValue(clickParams)); + new DreamValue(clickParams)).Dispose(); connection.LastClickTime = _timing.RealTime; } diff --git a/OpenDreamRuntime/Map/DreamMapManager.cs b/OpenDreamRuntime/Map/DreamMapManager.cs index 8d07ccc816..0d821a4f94 100644 --- a/OpenDreamRuntime/Map/DreamMapManager.cs +++ b/OpenDreamRuntime/Map/DreamMapManager.cs @@ -121,7 +121,7 @@ public void InitializeAtoms() { for (var x = 1; x <= Size.X; ++x) { var area = _levels[z - 1].Cells[x - 1, y - 1].Area; if (seenAreas.Add(area)) { - area.SpawnProc("New"); + area.SpawnProc("New").Dispose(); } } } @@ -131,7 +131,7 @@ public void InitializeAtoms() { // This may call New() a SECOND TIME. This is intentional. foreach (var thing in _atomManager.EnumerateAtoms(_objectTree.Area)) { if (seenAreas.Add(thing)) { - thing.SpawnProc("New"); + thing.SpawnProc("New").Dispose(); } } @@ -139,7 +139,7 @@ public void InitializeAtoms() { for (var z = 1; z <= Levels; ++z) { for (var y = Size.Y; y >= 1; --y) { for (var x = Size.X; x >= 1; --x) { - _levels[z - 1].Cells[x - 1, y - 1].Turf.SpawnProc("New"); + _levels[z - 1].Cells[x - 1, y - 1].Turf.SpawnProc("New").Dispose(); } } } @@ -293,6 +293,7 @@ public void SetWorldSize(Vector2i size) { defaultTurf.Cell = cell; existingLevel.Cells[x - 1, y - 1] = cell; SetTurf(new Vector2i(x, y), existingLevel.Z, defaultTurfDef, new()); + defaultTurf.DecRef(); } } } @@ -309,9 +310,11 @@ public void SetWorldSize(Vector2i size) { for (var y = 1; y <= oldSize.Y; y++) { if (x > size.X || y > size.Y) { var deleteCell = oldCells[x - 1, y - 1]; + deleteCell.Turf.DecRef(); deleteCell.Turf.Delete(); _mapSystem.SetTile(existingLevel.Grid, new Vector2i(x, y), Tile.Empty); foreach (var movableToDelete in deleteCell.Movables) { + movableToDelete.DecRef(); movableToDelete.Delete(); } } else { @@ -406,6 +409,7 @@ private void LoadMapObjectsAndMobs(MapBlockJson block, Dictionary varOverride in mapObject.VarOverrides) { if (definition.HasVariable(varOverride.Key)) { - definition.Variables[varOverride.Key] = _objectTree.GetDreamValueFromJsonElement(varOverride.Value); + using var overrideValue = _objectTree.GetDreamValueFromJsonElement(varOverride.Value); + + definition.Variables[varOverride.Key] = overrideValue; + overrideValue.IncRef(); } } } @@ -469,6 +476,7 @@ public Level(int z, Entity grid, DreamObjectDefinition turfTyp turf.Cell = cell; Cells[x, y] = cell; + turf.DecRef(); } } } @@ -497,6 +505,7 @@ public DreamObjectArea Area { public Cell(DreamObjectArea area, DreamObjectTurf turf) { Turf = turf; + Turf.IncRef(); _area = area; Area = area; } diff --git a/OpenDreamRuntime/Objects/DreamObject.cs b/OpenDreamRuntime/Objects/DreamObject.cs index f76e6c6faa..6c9aafad5a 100644 --- a/OpenDreamRuntime/Objects/DreamObject.cs +++ b/OpenDreamRuntime/Objects/DreamObject.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Runtime.CompilerServices; using DMCompiler.Bytecode; +using JetBrains.Annotations; using OpenDreamRuntime.Map; using OpenDreamRuntime.Objects.Types; using OpenDreamRuntime.Rendering; @@ -21,10 +22,13 @@ public class DreamObject { public DreamObjectDefinition ObjectDefinition; [Access(typeof(DreamObject))] - public bool Deleted; + public bool Deleting, Deleted; public readonly uint RefId; + [Access(typeof(DreamObject))] + public int RefCount = 1; // Starts at 1 because the code creating us is considered to hold a ref to us now + public virtual bool ShouldCallNew => true; // Shortcuts to IoC dependencies & entity systems @@ -99,23 +103,23 @@ public virtual void Initialize(DreamProcArguments args) { // For subtypes to implement } - protected virtual void HandleDeletion(bool possiblyThreaded) { - if (possiblyThreaded) { - // DeleteRef is not thread-safe, so pawn this off to the server thread - lock (DreamManager.RefDeleteQueue) { - DreamManager.RefDeleteQueue.Add(RefId); + protected virtual void HandleDeletion() { + // Remove the locate()-able reference to this object + // Freeing up the slot for later reuse, as well as the .NET hard ref + DreamRefManager.DeleteRef(RefId); + + if (Variables != null) { + foreach (var varValue in Variables.Values) { + varValue.DecRef(); } - } else { - // Remove the locate()-able reference to this object - // Freeing up the slot for later reuse - DreamRefManager.DeleteRef(RefId); } //we release all relevant information, making this a very tiny object Tag = null; Deleted = true; Variables = null; - _varsList?.Delete(possiblyThreaded); + _varsList?.DecRef(); + _varsList?.Delete(); _varsList = null; ObjectDefinition = null!; @@ -125,40 +129,39 @@ protected virtual void HandleDeletion(bool possiblyThreaded) { #endif } - /// - /// Enters the current dream object into a global del queue that is guaranteed to run on the DM thread. - /// Use if your deletion handler must be on the DM thread. - /// - protected void EnterIntoDelQueue() { - DreamManager.DelQueue.Add(this); + public void IncRef() { + if (Deleted || Deleting) + return; + + RefCount++; + } + + public void DecRef() { + if (Deleted || Deleting) + return; + + RefCount--; + if (RefCount == 0) + Delete(); } /// - /// Del() the object, cleaning up its variables and refs to minimize size until the .NET GC collects it. + /// Del() the object, cleaning up its variables and refs to allow the .NET GC to collect it. /// - /// If true, Delete() will be defensive and assume it may have been called from another thread by .NET - public void Delete(bool possiblyThreaded = false) { - if (Deleted) + public void Delete() { + if (Deleting || Deleted) return; + Deleting = true; if (TryGetProc("Del", out var delProc)) { - // SAFETY: See associated comment in Datum.dm. This relies on the invariant that this proc is in a - // thread-safe subset of DM (if such a thing exists) or empty. Currently, it is empty. + // Don't bother running Del() if there's no code in it var datumBaseProc = delProc is DMProc {Bytecode.Length: 0}; - if (possiblyThreaded && !datumBaseProc) { - EnterIntoDelQueue(); - return; //Whoops, cannot thread. - } else if (!datumBaseProc) { - DreamThread.Run(delProc, this, null); + if (!datumBaseProc) { + DreamThread.Run(delProc, this, null).Dispose(); } } - HandleDeletion(possiblyThreaded); - } - - ~DreamObject() { - // Softdel, possibly. - Delete(true); + HandleDeletion(); } public bool IsSubtypeOf(TreeEntry ancestor) { @@ -182,10 +185,11 @@ public bool HasVariable(string name) { return ObjectDefinition.HasVariable(name); } + [MustDisposeResource] public DreamValue GetVariable(string name) { DebugTools.Assert(!Deleted, "Cannot call GetVariable() on a deleted object"); - if (TryGetVariable(name, out DreamValue variableValue)) { + if (TryGetVariable(name, out var variableValue)) { return variableValue; } else { throw new KeyNotFoundException($"Variable {name} doesn't exist"); @@ -198,7 +202,7 @@ public IEnumerable GetVariableNames() { return ObjectDefinition.Variables.Keys; } - protected virtual bool TryGetVar(string varName, out DreamValue value) { + protected virtual bool TryGetVar(string varName, [MustDisposeResource] out DreamValue value) { switch (varName) { case "type": value = new(ObjectDefinition.TreeEntry); @@ -212,15 +216,19 @@ protected virtual bool TryGetVar(string varName, out DreamValue value) { return true; case "vars": _varsList ??= new DreamListVars(ObjectTree.List.ObjectDefinition, this); + _varsList.IncRef(); value = new(_varsList); return true; case "tag": value = (Tag != null) ? new(Tag) : DreamValue.Null; return true; default: - return (Variables?.TryGetValue(varName, out value) is true) || - (ObjectDefinition.Variables.TryGetValue(varName, out value)) || - (ObjectDefinition.GlobalVariables.TryGetValue(varName, out var globalIndex)) && ObjectDefinition.DreamManager.Globals.TryGetValue(globalIndex, out value); + var success = (Variables?.TryGetValue(varName, out value) is true) || + (ObjectDefinition.Variables.TryGetValue(varName, out value)) || + (ObjectDefinition.GlobalVariables.TryGetValue(varName, out var globalIndex)) && ObjectDefinition.DreamManager.Globals.TryGetValue(globalIndex, out value); + + value.IncRef(); + return success; } } @@ -246,7 +254,7 @@ protected virtual void SetVar(string varName, DreamValue value) { } } - public bool TryGetVariable(string name, out DreamValue variableValue) { + public bool TryGetVariable(string name, [MustDisposeResource] out DreamValue variableValue) { DebugTools.Assert(!Deleted, "Cannot call TryGetVariable() on a deleted object"); return TryGetVar(name, out variableValue); @@ -264,12 +272,14 @@ public void SetVariable(string name, DreamValue value) { /// /// Directly sets a variable's value, bypassing any special behavior /// - /// The OLD variable value [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetVariableValue(string name, DreamValue value) { DebugTools.Assert(!Deleted, "Cannot call SetVariableValue() on a deleted object"); Variables ??= new(4); + value.IncRef(); + if (Variables.TryGetValue(name, out var oldValue)) + oldValue.DecRef(); Variables[name] = value; } @@ -289,10 +299,11 @@ public bool TryGetProc(string procName, [NotNullWhen(true)] out DreamProc? proc) return ObjectDefinition.TryGetProc(procName, out proc); } - public void InitSpawn(DreamProcArguments creationArguments) { + public void InitSpawn([HandlesResourceDisposal] DreamProcArguments creationArguments) { if (ObjectDefinition.NoConstructors) { // Skip thread spinup. Initialize(creationArguments); + creationArguments.Dispose(); return; } @@ -300,10 +311,10 @@ public void InitSpawn(DreamProcArguments creationArguments) { var procState = InitProc(thread, null, creationArguments); thread.PushProcState(procState); - thread.Resume(); + thread.Resume().Dispose(); } - public ProcState InitProc(DreamThread thread, DreamObject? usr, DreamProcArguments arguments) { + public ProcState InitProc(DreamThread thread, DreamObject? usr, [HandlesResourceDisposal] DreamProcArguments arguments) { DebugTools.Assert(!Deleted, "Cannot call InitProc() on a deleted object"); if (!InitDreamObjectState.Pool.TryPop(out var state)) { @@ -314,6 +325,7 @@ public ProcState InitProc(DreamThread thread, DreamObject? usr, DreamProcArgumen return state; } + [MustDisposeResource] public DreamValue SpawnProc(string procName, DreamObject? usr = null, params DreamValue[] arguments) { DebugTools.Assert(!Deleted, "Cannot call SpawnProc() on a deleted object"); @@ -403,8 +415,11 @@ public string GetRawName() { if (this is DreamObjectAtom) { if (AtomManager.TryGetAppearance(this, out var appearance)) name = appearance.Name; - } else if (TryGetVariable("name", out DreamValue nameVar) && nameVar.TryGetValueAsString(out var nameVarStr)) { - name = nameVarStr; + } else if (TryGetVariable("name", out DreamValue nameVar)) { + if (nameVar.TryGetValueAsString(out var nameVarStr)) + name = nameVarStr; + + nameVar.Dispose(); } return name; @@ -415,6 +430,7 @@ public string GetRawName() { #region Operators // + + [MustDisposeResource] public virtual DreamValue OperatorAdd(DreamValue b, DMProcState state) { if (TryExecuteOperatorOverload(state, "operator+", new DreamProcArguments(b), out var result)) return result; @@ -423,6 +439,7 @@ public virtual DreamValue OperatorAdd(DreamValue b, DMProcState state) { } // - + [MustDisposeResource] public virtual DreamValue OperatorSubtract(DreamValue b, DMProcState state) { if (TryExecuteOperatorOverload(state, "operator-", new DreamProcArguments(b), out var result)) return result; @@ -431,6 +448,7 @@ public virtual DreamValue OperatorSubtract(DreamValue b, DMProcState state) { } // * + [MustDisposeResource] public virtual DreamValue OperatorMultiply(DreamValue b, DMProcState state) { if (TryExecuteOperatorOverload(state, "operator*", new DreamProcArguments(b), out var result)) return result; @@ -439,18 +457,18 @@ public virtual DreamValue OperatorMultiply(DreamValue b, DMProcState state) { } // *= + [MustDisposeResource] public virtual DreamValue OperatorMultiplyRef(DreamValue b, DMProcState state) { - var args = new DreamProcArguments(b); - if (TryExecuteOperatorOverload(state, "operator*=", args, out var result)) + if (TryExecuteOperatorOverload(state, "operator*=", new(b), out var result)) return result; - - if (TryExecuteOperatorOverload(state, "operator*", args, out result)) + if (TryExecuteOperatorOverload(state, "operator*", new(b), out result)) return result; throw new InvalidOperationException($"Multiplication cannot be done between {this} and {b}"); } // / + [MustDisposeResource] public virtual DreamValue OperatorDivide(DreamValue b, DMProcState state) { if (TryExecuteOperatorOverload(state, "operator/", new DreamProcArguments(b), out var result)) return result; @@ -459,6 +477,7 @@ public virtual DreamValue OperatorDivide(DreamValue b, DMProcState state) { } // /= + [MustDisposeResource] public virtual DreamValue OperatorDivideRef(DreamValue b, DMProcState state) { var args = new DreamProcArguments(b); if (TryExecuteOperatorOverload(state, "operator/=", args, out var result)) @@ -471,6 +490,7 @@ public virtual DreamValue OperatorDivideRef(DreamValue b, DMProcState state) { } // | + [MustDisposeResource] public virtual DreamValue OperatorOr(DreamValue b, DMProcState state) { if (TryExecuteOperatorOverload(state, "operator|", new DreamProcArguments(b), out var result)) return result; @@ -479,26 +499,31 @@ public virtual DreamValue OperatorOr(DreamValue b, DMProcState state) { } // += + [MustDisposeResource] public virtual DreamValue OperatorAppend(DreamValue b) { throw new InvalidOperationException($"Cannot append {b} to {this}"); } // -= + [MustDisposeResource] public virtual DreamValue OperatorRemove(DreamValue b) { throw new InvalidOperationException($"Cannot remove {b} from {this}"); } // |= + [MustDisposeResource] public virtual DreamValue OperatorCombine(DreamValue b) { throw new InvalidOperationException($"Cannot combine {this} and {b}"); } // &= + [MustDisposeResource] public virtual DreamValue OperatorMask(DreamValue b) { throw new InvalidOperationException($"Cannot mask {this} and {b}"); } // ~= + [MustDisposeResource] public virtual DreamValue OperatorEquivalent(DreamValue b) { if (!b.TryGetValueAsDreamObject(out var bObject)) return DreamValue.False; @@ -512,6 +537,7 @@ public virtual void OperatorOutput(DreamValue b) { } // [] + [MustDisposeResource] public virtual DreamValue OperatorIndex(DreamValue index, DMProcState state) { if (TryExecuteOperatorOverload(state, "operator[]", new DreamProcArguments(index), out var result)) return result; @@ -521,8 +547,10 @@ public virtual DreamValue OperatorIndex(DreamValue index, DMProcState state) { // []= public virtual void OperatorIndexAssign(DreamValue index, DMProcState state, DreamValue value) { - if (TryExecuteOperatorOverload(state, "operator[]=", new DreamProcArguments(index, value), out _)) + if (TryExecuteOperatorOverload(state, "operator[]=", new DreamProcArguments(index, value), out var result)) { + result.Dispose(); return; + } throw new InvalidOperationException($"Cannot assign {value} to index {index} of {this}"); } @@ -532,8 +560,8 @@ public virtual void OperatorIndexAssign(DreamValue index, DMProcState state, Dre private bool TryExecuteOperatorOverload( DMProcState parentState, string operatorName, - DreamProcArguments arguments, - out DreamValue procResult) { + [HandlesResourceDisposal] DreamProcArguments arguments, + [MustDisposeResource] out DreamValue procResult) { if (!TryGetProc(operatorName, out var proc)) { procResult = default; return false; @@ -571,4 +599,8 @@ public override string ToString() { return ObjectDefinition.Type; } + + public override int GetHashCode() { + return (int)RefId; + } } diff --git a/OpenDreamRuntime/Objects/DreamObjectTree.cs b/OpenDreamRuntime/Objects/DreamObjectTree.cs index 27d4b8b880..4ffeac8ead 100644 --- a/OpenDreamRuntime/Objects/DreamObjectTree.cs +++ b/OpenDreamRuntime/Objects/DreamObjectTree.cs @@ -3,6 +3,7 @@ using System.Text.Json; using System.Threading.Tasks; using DMCompiler.Json; +using JetBrains.Annotations; using OpenDreamRuntime.Map; using OpenDreamRuntime.Objects.Types; using OpenDreamRuntime.Procs; @@ -220,6 +221,7 @@ public DreamAssocList CreateAssocList(int size = 0) { return new DreamAssocList(AssocList.ObjectDefinition, size); } + [MustDisposeResource] public DreamValue GetDreamValueFromJsonElement(object? value) { if (value == null) return DreamValue.Null; @@ -264,10 +266,14 @@ public DreamValue GetDreamValueFromJsonElement(object? value) { !listValue.TryGetProperty("value", out var jsonValue)) throw new Exception("List value was missing a key or value property"); - list.SetValue(GetDreamValueFromJsonElement(jsonKey), - GetDreamValueFromJsonElement(jsonValue), allowGrowth: true); + using var listKey = GetDreamValueFromJsonElement(jsonKey); + using var dreamValue = GetDreamValueFromJsonElement(jsonValue); + + list.SetValue(listKey, dreamValue, allowGrowth: true); } else { - list.AddValue(GetDreamValueFromJsonElement(listValue)); + using var dreamValue = GetDreamValueFromJsonElement(listValue); + + list.AddValue(dreamValue); } } } @@ -285,8 +291,10 @@ public DreamValue GetDreamValueFromJsonElement(object? value) { throw new Exception("AList value was missing a key or value property"); } - aList.SetValue(GetDreamValueFromJsonElement(jsonKey), - GetDreamValueFromJsonElement(jsonValue)); + using var listKey = GetDreamValueFromJsonElement(jsonKey); + using var dreamValue = GetDreamValueFromJsonElement(jsonValue); + + aList.SetValue(listKey, dreamValue); } } @@ -434,8 +442,8 @@ private void LoadTypesFromJson(DreamTypeJson[] types, ProcDefinitionJson[]? proc private void LoadVariablesFromJson(DreamObjectDefinition objectDefinition, DreamTypeJson jsonObject) { if (jsonObject.Variables != null) { - foreach (KeyValuePair jsonVariable in jsonObject.Variables) { - DreamValue value = GetDreamValueFromJsonElement(jsonVariable.Value); + foreach (var jsonVariable in jsonObject.Variables) { + using var value = GetDreamValueFromJsonElement(jsonVariable.Value); objectDefinition.SetVariableDefinition(jsonVariable.Key, value); } diff --git a/OpenDreamRuntime/Objects/DreamRefManager.cs b/OpenDreamRuntime/Objects/DreamRefManager.cs index f085ec6d70..16fcd45d52 100644 --- a/OpenDreamRuntime/Objects/DreamRefManager.cs +++ b/OpenDreamRuntime/Objects/DreamRefManager.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using JetBrains.Annotations; using OpenDreamRuntime.Objects.Types; using OpenDreamRuntime.Rendering; using OpenDreamRuntime.Resources; @@ -29,67 +30,45 @@ public sealed class DreamRefManager { /// private sealed class Bucket { /// The amount of alive values in this bucket - public int FilledCount { get; private set; } + public int FilledCount => _values.Count - _emptySlots.Count; - private readonly List?> _values = new(); - private int _earliestEmptySlot; + private readonly List _values = new(); + private readonly Queue _emptySlots = new(); public int Add(DreamObject value) { - FilledCount++; + // We intentionally do not IncRef() here! This is a weak reference. - var refId = _earliestEmptySlot++; - if (refId >= _values.Count) { - _values.Add(new(value)); + if (_emptySlots.TryDequeue(out var refId)) { + _values[refId] = value; return refId; } - _values[refId] = new(value); - - // Find the next null value to update _earliestEmptySlot - for (; _earliestEmptySlot < _values.Count; _earliestEmptySlot++) { - if (_values[_earliestEmptySlot]?.TryGetTarget(out _) is true) - continue; - - _values[_earliestEmptySlot] = null; - break; - } - - return refId; + _values.Add(value); + return _values.Count - 1; } public DreamObject? Get(int refId) { - var weakRef = _values[refId]; - DreamObject? value = null; - - weakRef?.TryGetTarget(out value); + DreamObject? value = _values[refId]; + value?.IncRef(); return value; } public void Remove(int refId) { - if (refId >= _values.Count) + if (refId >= _values.Count || _values[refId] == null) return; - if (_values[refId] != null) - FilledCount--; _values[refId] = null; - _earliestEmptySlot = Math.Min(_earliestEmptySlot, refId); + _emptySlots.Enqueue(refId); } public IEnumerable Enumerate() { // This cannot ever be a foreach! // world.contents allows modification during enumeration for (var i = 0; i < _values.Count; i++) { - var weakRef = _values[i]; - if (weakRef is null) - continue; + var value = _values[i]; - if (weakRef.TryGetTarget(out var value)) { + if (value is not null) yield return value; - } else { - // This isn't a common operation so we'll use this time to also do some pruning. - FilledCount--; - _values[i] = null; - } } } } @@ -107,7 +86,8 @@ public void Initialize() { RefType.DreamObjectImage, RefType.DreamObjectFilter, RefType.DreamObjectMovable, - RefType.DreamObjectList + RefType.DreamObjectList, + RefType.DreamObjectListArgs ]; foreach (var type in bucketTypes) { @@ -177,6 +157,7 @@ public uint GetRef(DreamObject? dreamObject) { DreamObjectImage image => CreateRef(RefType.DreamObjectImage, image), DreamObjectFilter filter => CreateRef(RefType.DreamObjectFilter, filter), DreamObjectMovable => CreateRef(RefType.DreamObjectMovable, dreamObject), + ProcArgsList argsList => CreateRef(RefType.DreamObjectListArgs, argsList), DreamList list when list.GetType() == typeof(DreamList) => CreateRef(RefType.DreamObjectList, list), _ => CreateRef(RefType.DreamObjectDatum, dreamObject) }; @@ -215,6 +196,7 @@ public string GetRefString(DreamValue value) { /// May not return the same object that was passed to if it was deleted and its ID reused /// /// The number representation of a ref + [MustDisposeResource] public DreamValue LocateRef(uint @ref) { var refType = (RefType)(@ref & RefTypeMask); var refId = @ref & RefIdMask; @@ -272,6 +254,7 @@ public DreamValue LocateRef(uint @ref) { /// May not return the same object that was passed to if it was deleted and its ID reused /// /// The string representation of a ref, or a datum's tag + [MustDisposeResource] public DreamValue LocateRef(string refStr) { if (refStr.StartsWith('[') && refStr.EndsWith(']')) { // Strip the surrounding [] @@ -289,7 +272,10 @@ public DreamValue LocateRef(string refStr) { // Note that surrounding [] are stripped out at this point, this is intentional // Doing locate("[abc]") is the same as locate("abc") if (Tags.TryGetValue(refStr, out var tagList) && tagList.Count > 0) { - return new DreamValue(tagList[0]); + var located = tagList[0]; + + located.IncRef(); + return new DreamValue(located); } // Nothing found @@ -393,6 +379,7 @@ public enum RefType : uint { DreamResourceIcon = 0xC000000, DreamObjectImage = 0xD000000, DreamObjectList = 0xF000000, + DreamObjectListArgs = 0x10000000, DreamObjectDatum = 0x21000000, String = 0x6000000, DreamType = 0x9000000, //in byond type is from 0x8 to 0xb, but fuck that diff --git a/OpenDreamRuntime/Objects/Types/DreamAssocList.cs b/OpenDreamRuntime/Objects/Types/DreamAssocList.cs index 6a8522bfa4..01843f9130 100644 --- a/OpenDreamRuntime/Objects/Types/DreamAssocList.cs +++ b/OpenDreamRuntime/Objects/Types/DreamAssocList.cs @@ -1,4 +1,5 @@ using System.Linq; +using JetBrains.Annotations; using OpenDreamRuntime.Procs; namespace OpenDreamRuntime.Objects.Types; @@ -15,14 +16,31 @@ public DreamAssocList(DreamObjectDefinition listDef, Dictionary GetAssociativeValues() { public void RemoveValue(DreamValue value) { _values.Remove(value); + value.DecRef(); } public IEnumerable> EnumerateAssocValues() { @@ -87,6 +111,7 @@ public void AddValue(DreamValue value) { } _values[value] = DreamValue.Null; + value.IncRef(); } public IDreamList CreateCopy(int start = 1, int end = 0) { @@ -94,7 +119,12 @@ public IDreamList CreateCopy(int start = 1, int end = 0) { throw new Exception("list index out of bounds"); } - Dictionary copyValues = new(_values); + var copyValues = new Dictionary(_values); + foreach (var value in _values) { + value.Key.IncRef(); + value.Value.IncRef(); + } + return new DreamAssocList(ObjectDefinition, copyValues); } @@ -118,5 +148,4 @@ public void Swap(int index1, int index2) { public bool ContainsValue(DreamValue value) { return _values.ContainsKey(value); } - } diff --git a/OpenDreamRuntime/Objects/Types/DreamList.cs b/OpenDreamRuntime/Objects/Types/DreamList.cs index 1bf90f443f..10429c8f67 100644 --- a/OpenDreamRuntime/Objects/Types/DreamList.cs +++ b/OpenDreamRuntime/Objects/Types/DreamList.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; +using JetBrains.Annotations; using OpenDreamRuntime.Map; using OpenDreamRuntime.Procs; using OpenDreamRuntime.Rendering; @@ -41,10 +42,19 @@ public DreamList(DreamObjectDefinition listDef, List values, Diction _values = values; _associativeValues = associativeValues; - #if TOOLS + foreach (var value in _values) + value.IncRef(); + + if (_associativeValues != null) { + foreach (var assocValue in _associativeValues.Values) { + assocValue.IncRef(); + } + } + +#if TOOLS TracyMemoryId = Profiler.BeginMemoryZone(1, "/list instance"); UpdateTracyContentsMemory(); - #endif +#endif } public override void Initialize(DreamProcArguments args) { @@ -72,6 +82,7 @@ public override void Initialize(DreamProcArguments args) { list.AddValue(new DreamValue(newList)); newLists[i * size + j] = newList; + newList.DecRef(); } else { list.AddValue(DreamValue.Null); } @@ -84,23 +95,19 @@ public override void Initialize(DreamProcArguments args) { } // Hold on to large lists for reuse later - protected override void HandleDeletion(bool possiblyThreaded) { - if (_values.Capacity < DreamManager.ListPoolThreshold) { - base.HandleDeletion(possiblyThreaded); - return; - } - - if (possiblyThreaded) { - EnterIntoDelQueue(); - return; - } - - if (ListPool.Count < DreamManager.ListPoolSize) { + protected override void HandleDeletion() { + foreach (var value in _values) + value.DecRef(); + if (_associativeValues != null) + foreach (var assocValue in _associativeValues.Values) + assocValue.DecRef(); + + if (_values.Capacity > DreamManager.ListPoolThreshold && ListPool.Count < DreamManager.ListPoolSize) { _values.Clear(); ListPool.Push(_values); } - base.HandleDeletion(possiblyThreaded); + base.HandleDeletion(); } public IDreamList CreateCopy(int start = 1, int end = 0) { @@ -159,28 +166,43 @@ public Dictionary GetAssociativeValues() { return _associativeValues ??= new Dictionary(); } + [MustDisposeResource] public virtual DreamValue GetValue(DreamValue key) { if (key.TryGetValueAsInteger(out int keyInteger)) { - return _values[keyInteger - 1]; //1-indexed + var value = _values[keyInteger - 1]; //1-indexed + + value.IncRef(); + return value; } - if (_associativeValues == null) - return DreamValue.Null; + if (_associativeValues?.TryGetValue(key, out DreamValue assocValue) is true) { + assocValue.IncRef(); + return assocValue; + } - return _associativeValues.TryGetValue(key, out DreamValue value) ? value : DreamValue.Null; + return DreamValue.Null; } public virtual void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) { if (key.TryGetValueAsInteger(out int keyInteger)) { + value.IncRef(); + if (allowGrowth && keyInteger == _values.Count + 1) { _values.Add(value); } else { + _values[keyInteger - 1].DecRef(); _values[keyInteger - 1] = value; } } else { - if (!ContainsValue(key)) _values.Add(key); + if (!ContainsValue(key)) { + _values.Add(key); + key.IncRef(); + } _associativeValues ??= new Dictionary(1); + value.IncRef(); + if (_associativeValues.TryGetValue(key, out var oldValue)) + oldValue.DecRef(); _associativeValues[key] = value; } @@ -191,8 +213,13 @@ public virtual void RemoveValue(DreamValue value) { int valueIndex = _values.LastIndexOf(value); if (valueIndex != -1) { - _associativeValues?.Remove(value); + if (_associativeValues?.TryGetValue(value, out var associatedValue) is true) { + associatedValue.DecRef(); + _associativeValues.Remove(value); + } + _values.RemoveAt(valueIndex); + value.DecRef(); } UpdateTracyContentsMemory(); @@ -200,6 +227,7 @@ public virtual void RemoveValue(DreamValue value) { public virtual void AddValue(DreamValue value) { _values.Add(value); + value.IncRef(); UpdateTracyContentsMemory(); } @@ -236,23 +264,34 @@ public virtual void Cut(int start = 1, int end = 0) { if (end == 0 || end > (_values.Count + 1)) end = _values.Count + 1; if (_associativeValues != null) { - for (int i = start; i < end; i++) - _associativeValues.Remove(_values[i - 1]); + for (int i = start; i < end; i++) { + var key = _values[i - 1]; + + if (_associativeValues.Remove(key, out var assocValue)) { + assocValue.DecRef(); + } + } } - if (end > start) + if (end > start) { + for (int i = start; i < end; i++) { + _values[i - 1].DecRef(); + } + _values.RemoveRange(start - 1, end - start); + } UpdateTracyContentsMemory(); } public void Insert(int index, DreamValue value) { _values.Insert(index - 1, value); + value.IncRef(); UpdateTracyContentsMemory(); } public void Swap(int index1, int index2) { - DreamValue temp = GetValue(new DreamValue(index1)); + using var temp = GetValue(new DreamValue(index1)); SetValue(new DreamValue(index1), GetValue(new DreamValue(index2))); SetValue(new DreamValue(index2), temp); @@ -392,12 +431,14 @@ public override DreamValue OperatorAppend(DreamValue b) { if (bList._associativeValues?.TryGetValue(value, out var assocValue) is true) { // Ensure the associated value is correct _associativeValues ??= new(); _associativeValues[value] = assocValue; + assocValue.IncRef(); } } } else { AddValue(b); } + IncRef(); return new(this); } @@ -412,6 +453,7 @@ public override DreamValue OperatorRemove(DreamValue b) { RemoveValue(b); } + IncRef(); return new(this); } @@ -430,26 +472,32 @@ public override DreamValue OperatorCombine(DreamValue b) { AddValue(b); } + IncRef(); return new(this); } public override DreamValue OperatorMask(DreamValue b) { if (b.TryGetValueAsDreamList(out var bList)) { for (int i = 1; i <= GetLength(); i++) { - if (!bList.ContainsValue(GetValue(new DreamValue(i)))) { + using var value = GetValue(new DreamValue(i)); + + if (!bList.ContainsValue(value)) { Cut(i, i + 1); i--; } } } else { for (int i = 1; i <= GetLength(); i++) { - if (GetValue(new DreamValue(i)) != b) { + using var value = GetValue(new DreamValue(i)); + + if (value != b) { Cut(i, i + 1); i--; } } } + IncRef(); return new(this); } @@ -552,23 +600,16 @@ public override int FindValue(DreamValue value, int start = 1, int end = 0) { } // global.vars list -internal sealed class DreamGlobalVars : DreamList { - [Dependency] private readonly DreamManager _dreamMan = default!; - [Dependency] private readonly DreamObjectTree _objectTree = default!; - +internal sealed class DreamGlobalVars(DreamObjectDefinition listDef) : DreamList(listDef, 0) { public override bool IsAssociative => true; // We don't use the associative array but, yes, we behave like an associative list - public DreamGlobalVars(DreamObjectDefinition listDef) : base(listDef, 0) { - IoCManager.InjectDependencies(this); - } - public override List GetValues() { return EnumerateValues().ToList(); } public override IEnumerable EnumerateValues() { - var root = _objectTree.Root.ObjectDefinition; + var root = ObjectTree.Root.ObjectDefinition; foreach (var key in root.GlobalVariables.Keys) { yield return new DreamValue(key); @@ -580,7 +621,7 @@ public override bool ContainsKey(DreamValue value) { return false; } - return _objectTree.Root.ObjectDefinition.GlobalVariables.ContainsKey(varName); + return ObjectTree.Root.ObjectDefinition.GlobalVariables.ContainsKey(varName); } public override bool ContainsValue(DreamValue value) { @@ -592,22 +633,24 @@ public override DreamValue GetValue(DreamValue key) { throw new Exception($"Invalid var index {key}"); } - var root = _objectTree.Root.ObjectDefinition; + var root = ObjectTree.Root.ObjectDefinition; if (!root.GlobalVariables.TryGetValue(varName, out var globalId)) { throw new Exception($"Invalid global {varName}"); } - return _dreamMan.Globals[globalId]; + var value = DreamManager.Globals[globalId]; + value.IncRef(); + return value; } public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) { if (key.TryGetValueAsString(out var varName)) { - var root = _objectTree.Root.ObjectDefinition; + var root = ObjectTree.Root.ObjectDefinition; if (!root.GlobalVariables.TryGetValue(varName, out var globalId)) { throw new Exception($"Cannot set value of undefined global \"{varName}\""); } - _dreamMan.Globals[globalId] = value; + DreamManager.SetGlobal(globalId, value); } else { throw new Exception($"Invalid var index {key}"); } @@ -809,27 +852,14 @@ public override int FindValue(DreamValue value, int start = 1, int end = 0) { // atom.overlays or atom.underlays list // Operates on an object's appearance -public sealed class DreamOverlaysList : DreamList { - [Dependency] private readonly AtomManager _atomManager = default!; - private readonly ServerAppearanceSystem? _appearanceSystem; - private readonly DreamObject _owner; - private readonly bool _isUnderlays; - - public DreamOverlaysList(DreamObjectDefinition listDef, DreamObject owner, ServerAppearanceSystem? appearanceSystem, bool isUnderlays) : base(listDef, 0) { - IoCManager.InjectDependencies(this); - - _owner = owner; - _appearanceSystem = appearanceSystem; - _isUnderlays = isUnderlays; - } - +public sealed class DreamOverlaysList(DreamObjectDefinition listDef, DreamObject owner, ServerAppearanceSystem? appearanceSystem, bool isUnderlays) : DreamList(listDef, 0) { public override List GetValues() { return EnumerateValues().ToList(); } public override IEnumerable EnumerateValues() { - var appearance = _atomManager.MustGetAppearance(_owner); - if (_appearanceSystem == null) + var appearance = AtomManager.MustGetAppearance(owner); + if (appearanceSystem == null) yield break; foreach (var overlay in GetOverlaysArray(appearance)) { @@ -838,7 +868,7 @@ public override IEnumerable EnumerateValues() { } public override void Cut(int start = 1, int end = 0) { - _atomManager.UpdateAppearance(_owner, appearance => { + AtomManager.UpdateAppearance(owner, appearance => { var overlaysList = GetOverlaysList(appearance); int count = overlaysList.Count + 1; if (end == 0 || end > count) end = count; @@ -848,14 +878,14 @@ public override void Cut(int start = 1, int end = 0) { public override DreamValue GetValue(DreamValue key) { if (!key.TryGetValueAsInteger(out var overlayIndex) || overlayIndex < 1) - throw new Exception($"Invalid index into {(_isUnderlays ? "underlays" : "overlays")} list: {key}"); + throw new Exception($"Invalid index into {(isUnderlays ? "underlays" : "overlays")} list: {key}"); - ImmutableAppearance appearance = _atomManager.MustGetAppearance(_owner); + ImmutableAppearance appearance = AtomManager.MustGetAppearance(owner); var overlaysList = GetOverlaysArray(appearance); if (overlayIndex > overlaysList.Length) - throw new Exception($"Atom only has {overlaysList.Length} {(_isUnderlays ? "underlay" : "overlay")}(s), cannot index {overlayIndex}"); + throw new Exception($"Atom only has {overlaysList.Length} {(isUnderlays ? "underlay" : "overlay")}(s), cannot index {overlayIndex}"); - if (_appearanceSystem == null) + if (appearanceSystem == null) return DreamValue.Null; var overlayAppearance = overlaysList[overlayIndex - 1].ToMutable(); @@ -863,40 +893,40 @@ public override DreamValue GetValue(DreamValue key) { } public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) { - throw new Exception($"Cannot write to an index of an {(_isUnderlays ? "underlays" : "overlays")} list"); + throw new Exception($"Cannot write to an index of an {(isUnderlays ? "underlays" : "overlays")} list"); } public override void AddValue(DreamValue value) { - if (_appearanceSystem == null) + if (appearanceSystem == null) return; - var overlayAppearance = CreateOverlayAppearance(_atomManager, value, _atomManager.MustGetAppearance(_owner).Icon); - var immutableOverlay = _appearanceSystem.AddAppearance(overlayAppearance ?? MutableAppearance.Default); + var overlayAppearance = CreateOverlayAppearance(AtomManager, value, AtomManager.MustGetAppearance(owner).Icon); + var immutableOverlay = appearanceSystem.AddAppearance(overlayAppearance ?? MutableAppearance.Default); overlayAppearance?.Dispose(); //after UpdateAppearance is done, the atom is set with a new immutable appearance containing a hard ref to the overlay //only /mutable_appearance handles it differently, and that's done in DreamObjectImage - _atomManager.UpdateAppearance(_owner, appearance => { + AtomManager.UpdateAppearance(owner, appearance => { GetOverlaysList(appearance).Add(immutableOverlay); }); } public override void RemoveValue(DreamValue value) { - if (_appearanceSystem == null) + if (appearanceSystem == null) return; - MutableAppearance? overlayAppearance = CreateOverlayAppearance(_atomManager, value, _atomManager.MustGetAppearance(_owner).Icon); + MutableAppearance? overlayAppearance = CreateOverlayAppearance(AtomManager, value, AtomManager.MustGetAppearance(owner).Icon); if (overlayAppearance == null) return; - _atomManager.UpdateAppearance(_owner, appearance => { - GetOverlaysList(appearance).Remove(_appearanceSystem.AddAppearance(overlayAppearance, registerAppearance:false)); + AtomManager.UpdateAppearance(owner, appearance => { + GetOverlaysList(appearance).Remove(appearanceSystem.AddAppearance(overlayAppearance, registerAppearance:false)); overlayAppearance.Dispose(); }); } public override int GetLength() { - return GetOverlaysArray(_atomManager.MustGetAppearance(_owner)).Length; + return GetOverlaysArray(AtomManager.MustGetAppearance(owner)).Length; } public override int FindValue(DreamValue value, int start = 1, int end = 0) { @@ -905,11 +935,11 @@ public override int FindValue(DreamValue value, int start = 1, int end = 0) { [MethodImpl(MethodImplOptions.AggressiveInlining)] private List GetOverlaysList(MutableAppearance appearance) => - _isUnderlays ? appearance.Underlays : appearance.Overlays; + isUnderlays ? appearance.Underlays : appearance.Overlays; [MethodImpl(MethodImplOptions.AggressiveInlining)] private ImmutableAppearance[] GetOverlaysArray(ImmutableAppearance appearance) => - _isUnderlays ? appearance.Underlays : appearance.Overlays; + isUnderlays ? appearance.Underlays : appearance.Overlays; public static MutableAppearance? CreateOverlayAppearance(AtomManager atomManager, DreamValue value, int? defaultIcon) { MutableAppearance overlay; @@ -1106,6 +1136,7 @@ public override IEnumerable EnumerateValues() { filterObject.Filter = filter; yield return new DreamValue(filterObject); + filterObject.DecRef(); } } @@ -1174,6 +1205,11 @@ public sealed class ClientScreenList(DreamObjectTree objectTree, ServerScreenOve : DreamList(objectTree.List.ObjectDefinition, 0) { private readonly List _screenObjects = new(); + protected override void HandleDeletion() { + Cut(); + base.HandleDeletion(); + } + public override bool ContainsValue(DreamValue value) { return _screenObjects.Contains(value); } @@ -1203,6 +1239,7 @@ public override void AddValue(DreamValue value) { screenOverlaySystem?.AddScreenObject(connection, movable); _screenObjects.Add(value); + value.IncRef(); } public override void RemoveValue(DreamValue value) { @@ -1210,14 +1247,16 @@ public override void RemoveValue(DreamValue value) { return; screenOverlaySystem?.RemoveScreenObject(connection, movable); - _screenObjects.Remove(value); + if (_screenObjects.Remove(value)) + value.DecRef(); } public override void Cut(int start = 1, int end = 0) { if (end == 0 || end > _screenObjects.Count + 1) end = _screenObjects.Count + 1; for (int i = start - 1; i < end - 1; i++) { - if (!_screenObjects[i].TryGetValueAsDreamObject(out var movable)) + using var value = _screenObjects[i]; + if (!value.TryGetValueAsDreamObject(out var movable)) continue; screenOverlaySystem?.RemoveScreenObject(connection, movable); @@ -1402,6 +1441,8 @@ public override DreamValue GetValue(DreamValue key) { if (index < 1) break; + turf.IncRef(); + if (index == 1) // The index references this turf return new(turf); @@ -1409,10 +1450,16 @@ public override DreamValue GetValue(DreamValue key) { int contentsLength = turf.Contents.GetLength(); - if (index <= contentsLength) // The index references one of the turf's contents - return turf.Contents.GetValue(new(index)); + if (index <= contentsLength) { // The index references one of the turf's contents + var contentsItem = turf.Contents.GetValue(new(index)); + + contentsItem.IncRef(); + turf.DecRef(); + return contentsItem; + } index -= contentsLength; + turf.DecRef(); } throw new Exception($"Out of bounds index on turf contents list: {key}"); diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectArea.cs b/OpenDreamRuntime/Objects/Types/DreamObjectArea.cs index 2a5d56f77f..1693a8763b 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectArea.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectArea.cs @@ -39,6 +39,12 @@ public DreamObjectArea(DreamObjectDefinition objectDefinition) : base(objectDefi AtomManager.SetAtomAppearance(this, AtomManager.GetAppearanceFromDefinition(ObjectDefinition)); } + protected override void HandleDeletion() { + _contents.DecRef(); + _contents.Delete(); + base.HandleDeletion(); + } + /// /// Forces us to find the up-to-date "lowest" turf on next coordinate var access /// @@ -58,6 +64,7 @@ protected override bool TryGetVar(string varName, out DreamValue value) { value = new(Z); return true; case "contents": + _contents.IncRef(); value = new(_contents); return true; default: diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs b/OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs index 9383eea642..d6be67fc36 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs @@ -22,6 +22,16 @@ public string GetRTEntityDesc() { return ObjectDefinition.Type; } + protected override void HandleDeletion() { + Overlays.DecRef(); + Underlays.DecRef(); + VisContents.DecRef(); + Filters.DecRef(); + VisLocs?.DecRef(); + + base.HandleDeletion(); + } + protected override bool TryGetVar(string varName, out DreamValue value) { switch (varName) { // x/y/z/loc should be overriden by subtypes @@ -39,22 +49,27 @@ protected override bool TryGetVar(string varName, out DreamValue value) { value = new(appearanceCopy); return true; case "overlays": + Overlays.IncRef(); value = new(Overlays); return true; case "underlays": + Underlays.IncRef(); value = new(Underlays); return true; case "verbs": value = new(new VerbsList(ObjectTree, AtomManager, this)); return true; case "filters": + Filters.IncRef(); value = new(Filters); return true; case "vis_locs": VisLocs ??= ObjectTree.CreateList(); + VisLocs.IncRef(); value = new(VisLocs); return true; case "vis_contents": + VisContents.IncRef(); value = new(VisContents); return true; @@ -135,12 +150,15 @@ protected override void SetVar(string varName, DreamValue value) { // filters = list("type"=...) or list(filter(...), filter(...)) if (value.TryGetValueAsDreamList(out var valueList)) { - if (valueList.GetValue(new("type")) != DreamValue.Null) { // It's a single filter + using var typeArg = valueList.GetValue(new("type")); + + if (typeArg != DreamValue.Null) { // It's a single filter var filterObject = DreamObjectFilter.TryCreateFilter(ObjectTree, valueList); if (filterObject == null) // list() with invalid "type" is ignored break; Filters.AddValue(new(filterObject)); + filterObject.DecRef(); } else { // It's a list of filters foreach (var filter in valueList.EnumerateValues()) { if (!filter.TryGetValueAsDreamObject(out var filterObject)) { @@ -153,6 +171,7 @@ protected override void SetVar(string varName, DreamValue value) { } Filters.AddValue(new(filterObject)); + filterObject.DecRef(); } } } else if (!value.IsNull) { diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectCallee.cs b/OpenDreamRuntime/Objects/Types/DreamObjectCallee.cs index e06ea8a16b..7f06e4af79 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectCallee.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectCallee.cs @@ -38,9 +38,11 @@ protected override bool TryGetVar(string varName, out DreamValue value) { value = new(ProcState.Proc.GetSourceAtOffset(0).Line); return true; case "src": + ProcState.Instance?.IncRef(); value = new(ProcState.Instance); return true; case "usr": + ProcState.Usr?.IncRef(); value = new(ProcState.Usr); return true; case "type": diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs b/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs index a7f40518f6..209d6c870b 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs @@ -27,17 +27,15 @@ public DreamObjectClient(DreamObjectDefinition objectDefinition, DreamConnection View = DreamManager.WorldInstance.DefaultView; } - protected override void HandleDeletion(bool possiblyThreaded) { - // SAFETY: Client hashset is not threadsafe, this is not a hot path so no reason to change this. - if (possiblyThreaded) { - EnterIntoDelQueue(); - return; - } - + protected override void HandleDeletion() { Connection.Session?.Channel.Disconnect("Your client object was deleted"); DreamManager.Clients.Remove(this); - base.HandleDeletion(possiblyThreaded); + Screen.DecRef(); + ClientVerbs.DecRef(); + Images.DecRef(); + + base.HandleDeletion(); } protected override bool TryGetVar(string varName, out DreamValue value) { @@ -49,12 +47,15 @@ protected override bool TryGetVar(string varName, out DreamValue value) { value = new(Connection.Key); return true; case "mob": + Connection.Mob?.IncRef(); value = new(Connection.Mob); return true; case "statobj": + Connection.StatObj.IncRef(); value = Connection.StatObj; return true; case "eye": + Connection.Eye?.IncRef(); value = new(Connection.Eye); return true; case "view": @@ -94,15 +95,18 @@ protected override bool TryGetVar(string varName, out DreamValue value) { value = new("seeker"); return true; case "screen": + Screen.IncRef(); value = new(Screen); return true; case "verbs": + ClientVerbs.IncRef(); value = new(ClientVerbs); return true; case "show_popup_menus": value = new(ShowPopupMenus ? 1 : 0); return true; case "images": + Images.IncRef(); value = new(Images); return true; case "mouse_pointer_icon": @@ -122,6 +126,8 @@ protected override void SetVar(string varName, DreamValue value) { break; } case "statobj": + value.IncRef(); + Connection.StatObj.DecRef(); Connection.StatObj = value; break; case "eye": { @@ -130,6 +136,8 @@ protected override void SetVar(string varName, DreamValue value) { throw new Exception($"Cannot set eye to non-movable {value}"); // TODO: You can set it to a turf } + newEye?.IncRef(); + Connection.Eye?.DecRef(); Connection.Eye = newEye as DreamObjectMovable; break; } diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectDatabase.cs b/OpenDreamRuntime/Objects/Types/DreamObjectDatabase.cs index 62ac253ada..0750868144 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectDatabase.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectDatabase.cs @@ -23,14 +23,9 @@ public override void Initialize(DreamProcArguments args) { throw new DMCrashRuntime("Unable to open database."); } - protected override void HandleDeletion(bool possiblyThreaded) { - if (possiblyThreaded) { - EnterIntoDelQueue(); - return; - } - + protected override void HandleDeletion() { Close(); - base.HandleDeletion(possiblyThreaded); + base.HandleDeletion(); } /// diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectDatabaseQuery.cs b/OpenDreamRuntime/Objects/Types/DreamObjectDatabaseQuery.cs index 2ab4e3f7ee..3d6fb1954c 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectDatabaseQuery.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectDatabaseQuery.cs @@ -21,15 +21,10 @@ public override void Initialize(DreamProcArguments args) { SetupCommand(command, args.Values[1..]); } - protected override void HandleDeletion(bool possiblyThreaded) { - if (possiblyThreaded) { - EnterIntoDelQueue(); - return; - } - + protected override void HandleDeletion() { ClearCommand(); CloseReader(); - base.HandleDeletion(possiblyThreaded); + base.HandleDeletion(); } /// diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectException.cs b/OpenDreamRuntime/Objects/Types/DreamObjectException.cs index 9a13b2a527..0890fdc53a 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectException.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectException.cs @@ -11,36 +11,47 @@ public sealed class DreamObjectException(DreamObjectDefinition objectDefinition) protected override void SetVar(string varName, DreamValue value) { switch (varName) { case "name": + value.IncRef(); + Name.DecRef(); Name = value; return; case "desc": + value.IncRef(); + Desc.DecRef(); Desc = value; return; case "file": + value.IncRef(); + File.DecRef(); File = value; return; case "line": + value.IncRef(); + Line.DecRef(); Line = value; return; default: base.SetVar(varName, value); return; } - } protected override bool TryGetVar(string varName, out DreamValue value) { switch (varName) { case "name": + Name.IncRef(); value = Name; return true; case "desc": + Desc.IncRef(); value = Desc; return true; case "file": + File.IncRef(); value = File; return true; case "line": + Line.IncRef(); value = Line; return true; default: diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectFilter.cs b/OpenDreamRuntime/Objects/Types/DreamObjectFilter.cs index e104b84616..f2c4cc80da 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectFilter.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectFilter.cs @@ -11,16 +11,9 @@ public sealed class DreamObjectFilter(DreamObjectDefinition objectDefinition) : public DreamFilter Filter; - protected override void HandleDeletion(bool possiblyThreaded) { - // SAFETY: Attachment dictionary is not threadsafe, no reason to change this. - if (possiblyThreaded) { - EnterIntoDelQueue(); - return; - } - - base.HandleDeletion(possiblyThreaded); - + protected override void HandleDeletion() { FilterAttachedTo.Remove(Filter); + base.HandleDeletion(); } // TODO: Variable getting @@ -79,7 +72,7 @@ protected override void SetVar(string varName, DreamValue value) { if (!key.TryGetValueAsString(out var keyStr)) continue; - var value = list.GetValue(key); + using var value = list.GetValue(key); if (value.IsNull) continue; diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectGenerator.cs b/OpenDreamRuntime/Objects/Types/DreamObjectGenerator.cs index bfaf88a5a1..4d7608c0ca 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectGenerator.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectGenerator.cs @@ -44,6 +44,9 @@ public override void Initialize(DreamProcArguments args) { Generator = typeStr == "vector" ? new GeneratorVector2(low.AsVector2, high.AsVector2, distribution) : new GeneratorBox2(low.AsVector2, high.AsVector2, distribution); + + low.DecRef(); + high.DecRef(); break; } case "square": @@ -51,13 +54,18 @@ public override void Initialize(DreamProcArguments args) { var low = DreamObjectVector.CreateFromValue(a, ObjectTree); var high = DreamObjectVector.CreateFromValue(b, ObjectTree); - Generator = typeStr switch { - "square" => new GeneratorSquare(low.AsVector2, high.AsVector2, distribution), - "cube" => new GeneratorCube(low.AsVector3, high.AsVector3, distribution), - _ => throw new ArgumentOutOfRangeException() - }; + try { + Generator = typeStr switch { + "square" => new GeneratorSquare(low.AsVector2, high.AsVector2, distribution), + "cube" => new GeneratorCube(low.AsVector3, high.AsVector3, distribution), + _ => throw new ArgumentOutOfRangeException() + }; - break; + break; + } finally { + low.DecRef(); + high.DecRef(); + } } default: throw new Exception($"Invalid generator type {type}"); diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectImage.cs b/OpenDreamRuntime/Objects/Types/DreamObjectImage.cs index 49b540d249..49489a5eef 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectImage.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectImage.cs @@ -85,21 +85,29 @@ protected override bool TryGetVar(string varName, out DreamValue value) { // TODO: filters, transform switch(varName) { case "loc": { + _loc?.IncRef(); value = new(_loc); return true; } case "overlays": + _overlays.IncRef(); value = new(_overlays); return true; case "underlays": + _underlays.IncRef(); value = new(_underlays); return true; case "filters": + _filters.IncRef(); value = new(_filters); return true; default: { if (AtomManager.IsValidAppearanceVar(varName)) { - value = IsMutableAppearance ? AtomManager.GetAppearanceVar(MutableAppearance!, varName) : AtomManager.GetAppearanceVar(AtomManager.MustGetAppearance(this), varName); + if (IsMutableAppearance) + value = AtomManager.GetAppearanceVar(MutableAppearance!, varName); + else + value = AtomManager.GetAppearanceVar(AtomManager.MustGetAppearance(this), varName); + return true; } else { return base.TryGetVar(varName, out value); @@ -121,6 +129,7 @@ protected override void SetVar(string varName, DreamValue value) { newAppearance.Dispose(); break; case "loc": + _loc?.DecRef(); value.TryGetValueAsDreamObject(out _loc); break; case "overlays": { @@ -132,6 +141,7 @@ protected override void SetVar(string varName, DreamValue value) { // Otherwise it attempts to create an appearance and creates a new (normal) list with that appearance if (ObjectDefinition.IsSubtypeOf(ObjectTree.MutableAppearance)) { if (valueList != null) { + _overlays.DecRef(); _overlays = (DreamList)valueList.CreateCopy(); } else { var overlay = DreamOverlaysList.CreateOverlayAppearance(AtomManager, value, AtomManager.MustGetAppearance(this).Icon); @@ -165,6 +175,7 @@ protected override void SetVar(string varName, DreamValue value) { // See the comment in the overlays setter for info on this if (ObjectDefinition.IsSubtypeOf(ObjectTree.MutableAppearance)) { if (valueList != null) { + _underlays.DecRef(); _underlays = (DreamList)valueList.CreateCopy(); } else { var underlay = DreamOverlaysList.CreateOverlayAppearance(AtomManager, value, AtomManager.MustGetAppearance(this).Icon); @@ -199,12 +210,15 @@ protected override void SetVar(string varName, DreamValue value) { // filters = list("type"=...) or list(filter(...), filter(...)) if (valueList != null) { // filters = list("type"=...) - if (valueList.GetValue(new("type")) != DreamValue.Null) { // It's a single filter + using var typeArg = valueList.GetValue(new("type")); + + if (typeArg != DreamValue.Null) { // It's a single filter var filterObject = DreamObjectFilter.TryCreateFilter(ObjectTree, valueList); if (filterObject == null) // list() with invalid "type" is ignored break; _filters.AddValue(new(filterObject)); + filterObject.DecRef(); } else { // It's a list of filters foreach (var filter in valueList.EnumerateValues()) { if (!filter.TryGetValueAsDreamObject(out var filterObject)) { @@ -217,6 +231,7 @@ protected override void SetVar(string varName, DreamValue value) { } _filters.AddValue(new(filterObject)); + filterObject.DecRef(); } } } else if (!value.IsNull) { @@ -248,18 +263,15 @@ protected override void SetVar(string varName, DreamValue value) { return _loc; } - protected override void HandleDeletion(bool possiblyThreaded) { - // SAFETY: Deleting entities is not threadsafe. - if (possiblyThreaded) { - EnterIntoDelQueue(); - return; - } - - if(Entity != EntityUid.Invalid) { + protected override void HandleDeletion() { + if (Entity != EntityUid.Invalid) { EntityManager.DeleteEntity(Entity); } MutableAppearance?.Dispose(); - base.HandleDeletion(possiblyThreaded); + _overlays.DecRef(); + _underlays.DecRef(); + _filters.DecRef(); + base.HandleDeletion(); } } diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectMatrix.cs b/OpenDreamRuntime/Objects/Types/DreamObjectMatrix.cs index a4b3e3d7c4..249a35272f 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectMatrix.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectMatrix.cs @@ -4,25 +4,35 @@ namespace OpenDreamRuntime.Objects.Types; -public sealed class DreamObjectMatrix : DreamObject { +public sealed class DreamObjectMatrix(DreamObjectDefinition objectDefinition) : DreamObject(objectDefinition) { public static readonly float[] IdentityMatrixArray = {1f, 0f, 0f, 0f, 1f, 0f}; - // TODO: Store a/b/c/d/e/f as fields instead of as DM vars + public float A { get=> _aInner.UnsafeGetValueAsFloat(); set { _aInner.DecRef(); _aInner = new(value); } } + public float B { get=> _bInner.UnsafeGetValueAsFloat(); set { _bInner.DecRef(); _bInner = new(value); } } + public float C { get=> _cInner.UnsafeGetValueAsFloat(); set { _cInner.DecRef(); _cInner = new(value); } } + public float D { get=> _dInner.UnsafeGetValueAsFloat(); set { _dInner.DecRef(); _dInner = new(value); } } + public float E { get=> _eInner.UnsafeGetValueAsFloat(); set { _eInner.DecRef(); _eInner = new(value); } } + public float F { get=> _fInner.UnsafeGetValueAsFloat(); set { _fInner.DecRef(); _fInner = new(value); } } - public DreamObjectMatrix(DreamObjectDefinition objectDefinition) : base(objectDefinition) { - - } + private DreamValue _aInner, _bInner, _cInner, _dInner, _eInner, _fInner; public override void Initialize(DreamProcArguments args) { - if (args.Count > 0) { + if (args.Count == 0) { + A = 1f; + B = 0f; + C = 0f; + D = 0f; + E = 1f; + F = 0f; + } else { DreamValue copyMatrixOrA = args.GetArgument(0); if (copyMatrixOrA.TryGetValueAsDreamObject(out var matrixToCopy)) { - SetVariableValue("a", matrixToCopy.GetVariable("a")); - SetVariableValue("b", matrixToCopy.GetVariable("b")); - SetVariableValue("c", matrixToCopy.GetVariable("c")); - SetVariableValue("d", matrixToCopy.GetVariable("d")); - SetVariableValue("e", matrixToCopy.GetVariable("e")); - SetVariableValue("f", matrixToCopy.GetVariable("f")); + A = matrixToCopy.A; + B = matrixToCopy.B; + C = matrixToCopy.C; + D = matrixToCopy.D; + E = matrixToCopy.E; + F = matrixToCopy.F; } else { DreamValue b = args.GetArgument(1); DreamValue c = args.GetArgument(2); @@ -30,53 +40,58 @@ public override void Initialize(DreamProcArguments args) { DreamValue e = args.GetArgument(4); DreamValue f = args.GetArgument(5); try { // BYOND runtimes if args are of the wrong type - copyMatrixOrA.MustGetValueAsFloat(); - b.MustGetValueAsFloat(); - c.MustGetValueAsFloat(); - d.MustGetValueAsFloat(); - e.MustGetValueAsFloat(); - f.MustGetValueAsFloat(); + A = copyMatrixOrA.MustGetValueAsFloat(); + B = b.MustGetValueAsFloat(); + C = c.MustGetValueAsFloat(); + D = d.MustGetValueAsFloat(); + E = e.MustGetValueAsFloat(); + F = f.MustGetValueAsFloat(); } catch (InvalidCastException) { throw new ArgumentException($"Invalid arguments used to create matrix {copyMatrixOrA} {b} {c} {d} {e} {f}"); } - - SetVariableValue("a", copyMatrixOrA); - SetVariableValue("b", b); - SetVariableValue("c", c); - SetVariableValue("d", d); - SetVariableValue("e", e); - SetVariableValue("f", f); } } base.Initialize(args); } + protected override bool TryGetVar(string varName, out DreamValue value) { + switch (varName) { + case "a": value = _aInner; return true; + case "b": value = _bInner; return true; + case "c": value = _cInner; return true; + case "d": value = _dInner; return true; + case "e": value = _eInner; return true; + case "f": value = _fInner; return true; + default: return base.TryGetVar(varName, out value); + } + } + + protected override void SetVar(string varName, DreamValue value) { + switch (varName) { + case "a": _aInner.DecRef(); _aInner = value; _aInner.IncRef(); break; + case "b": _bInner.DecRef(); _bInner = value; _bInner.IncRef(); break; + case "c": _cInner.DecRef(); _cInner = value; _cInner.IncRef(); break; + case "d": _dInner.DecRef(); _dInner = value; _dInner.IncRef(); break; + case "e": _eInner.DecRef(); _eInner = value; _eInner.IncRef(); break; + case "f": _fInner.DecRef(); _fInner = value; _fInner.IncRef(); break; + default: + base.SetVar(varName, value); + break; + } + } + #region Operators public override DreamValue OperatorAdd(DreamValue b, DMProcState state) { - GetVariable("a").TryGetValueAsFloat(out float lA); - GetVariable("b").TryGetValueAsFloat(out float lB); - GetVariable("c").TryGetValueAsFloat(out float lC); - GetVariable("d").TryGetValueAsFloat(out float lD); - GetVariable("e").TryGetValueAsFloat(out float lE); - GetVariable("f").TryGetValueAsFloat(out float lF); - if (b.TryGetValueAsDreamObject(out var right)) { - right.GetVariable("a").TryGetValueAsFloat(out float rA); - right.GetVariable("b").TryGetValueAsFloat(out float rB); - right.GetVariable("c").TryGetValueAsFloat(out float rC); - right.GetVariable("d").TryGetValueAsFloat(out float rD); - right.GetVariable("e").TryGetValueAsFloat(out float rE); - right.GetVariable("f").TryGetValueAsFloat(out float rF); - DreamObject output = MakeMatrix(ObjectTree, - lA + rA, // a - lB + rB, // b - lC + rC, // c - lD + rD, // d - lE + rE, // e - lF + rF // f + A + right.A, // a + B + right.B, // b + C + right.C, // c + D + right.D, // d + E + right.E, // e + F + right.F // f ); return new DreamValue(output); @@ -86,28 +101,14 @@ public override DreamValue OperatorAdd(DreamValue b, DMProcState state) { } public override DreamValue OperatorSubtract(DreamValue b, DMProcState state) { - GetVariable("a").TryGetValueAsFloat(out float lA); - GetVariable("b").TryGetValueAsFloat(out float lB); - GetVariable("c").TryGetValueAsFloat(out float lC); - GetVariable("d").TryGetValueAsFloat(out float lD); - GetVariable("e").TryGetValueAsFloat(out float lE); - GetVariable("f").TryGetValueAsFloat(out float lF); - if (b.TryGetValueAsDreamObject(out var right)) { - right.GetVariable("a").TryGetValueAsFloat(out float rA); - right.GetVariable("b").TryGetValueAsFloat(out float rB); - right.GetVariable("c").TryGetValueAsFloat(out float rC); - right.GetVariable("d").TryGetValueAsFloat(out float rD); - right.GetVariable("e").TryGetValueAsFloat(out float rE); - right.GetVariable("f").TryGetValueAsFloat(out float rF); - DreamObject output = MakeMatrix(ObjectTree, - lA - rA, // a - lB - rB, // b - lC - rC, // c - lD - rD, // d - lE - rE, // e - lF - rF // f + A - right.A, // a + B - right.B, // b + C - right.C, // c + D - right.D, // d + E - right.E, // e + F - right.F // f ); return new DreamValue(output); @@ -117,34 +118,21 @@ public override DreamValue OperatorSubtract(DreamValue b, DMProcState state) { } public override DreamValue OperatorMultiply(DreamValue b, DMProcState state) { - GetVariable("a").TryGetValueAsFloat(out float lA); - GetVariable("b").TryGetValueAsFloat(out float lB); - GetVariable("c").TryGetValueAsFloat(out float lC); - GetVariable("d").TryGetValueAsFloat(out float lD); - GetVariable("e").TryGetValueAsFloat(out float lE); - GetVariable("f").TryGetValueAsFloat(out float lF); - if (b.TryGetValueAsFloat(out float bFloat)) { DreamObjectMatrix output = MakeMatrix(ObjectTree, - lA * bFloat,lB * bFloat,lC * bFloat, - lD * bFloat,lE * bFloat,lF * bFloat - ); + A * bFloat, B * bFloat, C * bFloat, + D * bFloat, E * bFloat, F * bFloat + ); + return new DreamValue(output); } else if (b.TryGetValueAsDreamObject(out var right)) { - right.GetVariable("a").TryGetValueAsFloat(out float rA); - right.GetVariable("b").TryGetValueAsFloat(out float rB); - right.GetVariable("c").TryGetValueAsFloat(out float rC); - right.GetVariable("d").TryGetValueAsFloat(out float rD); - right.GetVariable("e").TryGetValueAsFloat(out float rE); - right.GetVariable("f").TryGetValueAsFloat(out float rF); - DreamObjectMatrix output = MakeMatrix(ObjectTree, - rA * lA + rD * lB, // a - rB * lA + rE * lB, // b - rC * lA + rF * lB + lC, // c - rA * lD + rD * lE, // d - rB * lD + rE * lE, // e - rC * lD + rF * lE + lF // f + right.A * A + right.D * B, // a + right.B * A + right.E * B, // b + right.C * A + right.F * B + C, // c + right.A * D + right.D * E, // d + right.B * D + right.E * E, // e + right.C * D + right.F * E + F // f ); return new DreamValue(output); @@ -158,24 +146,19 @@ public override DreamValue OperatorMultiplyRef(DreamValue b, DMProcState state) } public override DreamValue OperatorDivide(DreamValue b, DMProcState state) { - GetVariable("a").TryGetValueAsFloat(out float lA); - GetVariable("b").TryGetValueAsFloat(out float lB); - GetVariable("c").TryGetValueAsFloat(out float lC); - GetVariable("d").TryGetValueAsFloat(out float lD); - GetVariable("e").TryGetValueAsFloat(out float lE); - GetVariable("f").TryGetValueAsFloat(out float lF); - if (b.TryGetValueAsFloat(out float bFloat)) { DreamObjectMatrix output = MakeMatrix(ObjectTree, - lA / bFloat,lB / bFloat,lC / bFloat, - lD / bFloat,lE / bFloat,lF / bFloat + A / bFloat, B / bFloat, C / bFloat, + D / bFloat, E / bFloat, F / bFloat ); return new DreamValue(output); } else if(b.TryGetValueAsDreamObject(out var right)) { //matrix divided by matrix isn't a thing, but in BYOND it's apparently multiplication by the inverse, because of course it is - DreamObjectMatrix rightCopy = MatrixClone(ObjectTree, right); + var rightCopy = MatrixClone(ObjectTree, right); + using var rightCopyDreamValue = new DreamValue(rightCopy); if (!TryInvert(rightCopy)) throw new ArgumentException("Matrix does not have a valid inversion for Invert()"); - return OperatorMultiply(new(rightCopy), state); + + return OperatorMultiply(rightCopyDreamValue, state); } return base.OperatorDivide(b, state); @@ -189,39 +172,22 @@ public override DreamValue OperatorEquivalent(DreamValue b) { if (!b.TryGetValueAsDreamObject(out var right)) return DreamValue.False; - const string elements = "abcdef"; - for (int i = 0; i < elements.Length; i++) { - GetVariable(elements[i].ToString()).TryGetValueAsFloat(out var leftValue); // sets leftValue to 0 if this isn't a float - right.GetVariable(elements[i].ToString()).TryGetValueAsFloat(out var rightValue); // ditto - if (!leftValue.Equals(rightValue)) - return DreamValue.False; - } - return DreamValue.True; + return A.Equals(right.A) && B.Equals(right.B) && + C.Equals(right.C) && D.Equals(right.D) && + E.Equals(right.E) && F.Equals(right.F) + ? DreamValue.True + : DreamValue.False; } public override DreamValue OperatorAppend(DreamValue b) { - GetVariable("a").TryGetValueAsFloat(out float lA); - GetVariable("b").TryGetValueAsFloat(out float lB); - GetVariable("c").TryGetValueAsFloat(out float lC); - GetVariable("d").TryGetValueAsFloat(out float lD); - GetVariable("e").TryGetValueAsFloat(out float lE); - GetVariable("f").TryGetValueAsFloat(out float lF); - if (b.TryGetValueAsDreamObject(out var right)) { - right.GetVariable("a").TryGetValueAsFloat(out float rA); - right.GetVariable("b").TryGetValueAsFloat(out float rB); - right.GetVariable("c").TryGetValueAsFloat(out float rC); - right.GetVariable("d").TryGetValueAsFloat(out float rD); - right.GetVariable("e").TryGetValueAsFloat(out float rE); - right.GetVariable("f").TryGetValueAsFloat(out float rF); - - SetVariableValue("a", new DreamValue(lA + rA)); - SetVariableValue("b", new DreamValue(lB + rB)); - SetVariableValue("c", new DreamValue(lC + rC)); - SetVariableValue("d", new DreamValue(lD + rD)); - SetVariableValue("e", new DreamValue(lE + rE)); - SetVariableValue("f", new DreamValue(lF + rF)); - + A += right.A; + B += right.B; + C += right.C; + D += right.D; + E += right.E; + F += right.F; + IncRef(); return new(this); } @@ -229,28 +195,14 @@ public override DreamValue OperatorAppend(DreamValue b) { } public override DreamValue OperatorRemove(DreamValue b) { - GetVariable("a").TryGetValueAsFloat(out float lA); - GetVariable("b").TryGetValueAsFloat(out float lB); - GetVariable("c").TryGetValueAsFloat(out float lC); - GetVariable("d").TryGetValueAsFloat(out float lD); - GetVariable("e").TryGetValueAsFloat(out float lE); - GetVariable("f").TryGetValueAsFloat(out float lF); - if (b.TryGetValueAsDreamObject(out var right)) { - right.GetVariable("a").TryGetValueAsFloat(out float rA); - right.GetVariable("b").TryGetValueAsFloat(out float rB); - right.GetVariable("c").TryGetValueAsFloat(out float rC); - right.GetVariable("d").TryGetValueAsFloat(out float rD); - right.GetVariable("e").TryGetValueAsFloat(out float rE); - right.GetVariable("f").TryGetValueAsFloat(out float rF); - - SetVariableValue("a", new DreamValue(lA - rA)); - SetVariableValue("b", new DreamValue(lB - rB)); - SetVariableValue("c", new DreamValue(lC - rC)); - SetVariableValue("d", new DreamValue(lD - rD)); - SetVariableValue("e", new DreamValue(lE - rE)); - SetVariableValue("f", new DreamValue(lF - rF)); - + A -= right.A; + B -= right.B; + C -= right.C; + D -= right.D; + E -= right.E; + F -= right.F; + IncRef(); return new(this); } @@ -262,15 +214,14 @@ public override DreamValue OperatorRemove(DreamValue b) { /// 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. - /// This will not verify that this is a /matrix public static float[] MatrixToTransformFloatArray(DreamObjectMatrix matrix) { float[] array = new float[6]; - matrix.GetVariable("a").TryGetValueAsFloat(out array[0]); - matrix.GetVariable("d").TryGetValueAsFloat(out array[1]); - matrix.GetVariable("b").TryGetValueAsFloat(out array[2]); - matrix.GetVariable("e").TryGetValueAsFloat(out array[3]); - matrix.GetVariable("c").TryGetValueAsFloat(out array[4]); - matrix.GetVariable("f").TryGetValueAsFloat(out array[5]); + array[0] = matrix.A; + array[1] = matrix.D; + array[2] = matrix.B; + array[3] = matrix.E; + array[4] = matrix.C; + array[5] = matrix.F; return array; } @@ -304,12 +255,12 @@ public static DreamObjectMatrix MatrixClone(DreamObjectTree objectTree, DreamObj /// A matrix created with a to f manually set to the floats given. public static DreamObjectMatrix MakeMatrix(DreamObjectTree objectTree, float a, float b, float c, float d, float e, float f) { var newMatrix = objectTree.CreateObject(objectTree.Matrix); - newMatrix.SetVariableValue("a", new(a)); - newMatrix.SetVariableValue("b", new(b)); - newMatrix.SetVariableValue("c", new(c)); - newMatrix.SetVariableValue("d", new(d)); - newMatrix.SetVariableValue("e", new(e)); - newMatrix.SetVariableValue("f", new(f)); + newMatrix.A = a; + newMatrix.B = b; + newMatrix.C = c; + newMatrix.D = d; + newMatrix.E = e; + newMatrix.F = f; return newMatrix; } @@ -323,16 +274,8 @@ public static DreamObjectMatrix MakeMatrix(DreamObjectTree objectTree, float[] m } public static float Determinant(DreamObjectMatrix matrix) { - try { - return matrix.GetVariable("a").MustGetValueAsFloat() * - matrix.GetVariable("e").MustGetValueAsFloat() - - matrix.GetVariable("d").MustGetValueAsFloat() * - matrix.GetVariable("b").MustGetValueAsFloat(); - } catch(InvalidCastException) { - return 0f; - } catch(KeyNotFoundException) { - return 0f; - } + return matrix.A * matrix.E - + matrix.D * matrix.B; } /// Inverts the given matrix, in-place. @@ -341,26 +284,16 @@ public static bool TryInvert(DreamObjectMatrix matrix) { var determinant = Determinant(matrix); if (determinant == 0f) return false; + var oldValues = EnumerateMatrix(matrix).ToImmutableArray(); + //Just going by what we used to have as DM code within DMStandard. No clue if the math is right, here - matrix.SetVariableValue("a", new DreamValue( // a = e - oldValues[4] / determinant - )); - matrix.SetVariableValue("b", new DreamValue( // b = -b - -oldValues[1] / determinant - )); - matrix.SetVariableValue("c", new DreamValue( // c = b*f - e*c - (oldValues[1] * oldValues[5] - oldValues[4] * oldValues[2]) / determinant - )); - matrix.SetVariableValue("d", new DreamValue( // d = -d - -oldValues[3] / determinant - )); - matrix.SetVariableValue("e", new DreamValue( // e = a - oldValues[0] / determinant - )); - matrix.SetVariableValue("f", new DreamValue( // f = d*c - a*f - (oldValues[3] * oldValues[2] - oldValues[0] * oldValues[5]) / determinant - )); + matrix.A = oldValues[4] / determinant; // a = e + matrix.B = -oldValues[1] / determinant; // b = -b + matrix.C = (oldValues[1] * oldValues[5] - oldValues[4] * oldValues[2]) / determinant; // c = b*f - e*c + matrix.D = -oldValues[3] / determinant; // d = -d + matrix.E = oldValues[0] / determinant; // e = a + matrix.F = (oldValues[3] * oldValues[2] - oldValues[0] * oldValues[5]) / determinant; // f = d*c - a*f return true; } @@ -368,170 +301,67 @@ public static bool TryInvert(DreamObjectMatrix matrix) { /// Used when printing this matrix to enumerate its values in order. /// /// The matrix's values in [a,b,c,d,e,f] order. - /// This will not verify that this is a /matrix public static IEnumerable EnumerateMatrix(DreamObjectMatrix matrix) { - float ret = 0f; - matrix.GetVariable("a").TryGetValueAsFloat(out ret); - yield return ret; - matrix.GetVariable("b").TryGetValueAsFloat(out ret); - yield return ret; - matrix.GetVariable("c").TryGetValueAsFloat(out ret); - yield return ret; - matrix.GetVariable("d").TryGetValueAsFloat(out ret); - yield return ret; - matrix.GetVariable("e").TryGetValueAsFloat(out ret); - yield return ret; - matrix.GetVariable("f").TryGetValueAsFloat(out ret); - yield return ret; + yield return matrix.A; + yield return matrix.B; + yield return matrix.C; + yield return matrix.D; + yield return matrix.E; + yield return matrix.F; } /// Translates a given matrix by the two translation factors given. /// Note that this does use . /// Thrown if the matrix has non-float members. public static void TranslateMatrix(DreamObjectMatrix matrix, float x, float y) { - try { - matrix.SetVariableValue("c", new DreamValue(matrix.GetVariable("c").MustGetValueAsFloat() + x)); - matrix.SetVariableValue("f", new DreamValue(matrix.GetVariable("f").MustGetValueAsFloat() + y)); - } catch(InvalidCastException) { // If any of these MustGet()s fail, try to give a more descriptive runtime - throw new InvalidOperationException($"Invalid matrix '{matrix}' cannot be scaled"); - } + matrix.C += x; + matrix.F += y; } /// Scales a given matrix by the two scaling factors given. /// Note that this does use . /// Thrown if the matrix has non-float members. public static void ScaleMatrix(DreamObjectMatrix matrix, float x, float y) { - try { - matrix.SetVariableValue("a", new DreamValue(matrix.GetVariable("a").MustGetValueAsFloat() * x)); - matrix.SetVariableValue("b", new DreamValue(matrix.GetVariable("b").MustGetValueAsFloat() * x)); - matrix.SetVariableValue("c", new DreamValue(matrix.GetVariable("c").MustGetValueAsFloat() * x)); - - matrix.SetVariableValue("d", new DreamValue(matrix.GetVariable("d").MustGetValueAsFloat() * y)); - matrix.SetVariableValue("e", new DreamValue(matrix.GetVariable("e").MustGetValueAsFloat() * y)); - matrix.SetVariableValue("f", new DreamValue(matrix.GetVariable("f").MustGetValueAsFloat() * y)); - } catch(InvalidCastException) { // If any of these MustGet()s fail, try to give a more descriptive runtime - throw new InvalidOperationException($"Invalid matrix '{matrix}' cannot be scaled"); - } + matrix.A *= x; + matrix.B *= x; + matrix.C *= x; + + matrix.D *= y; + matrix.E *= y; + matrix.F *= y; } /// Adds the second given matrix to the first given matrix. - /// Note that this does use . - /// Thrown if either matrix has non-float members. public static void AddMatrix(DreamObjectMatrix lMatrix, DreamObjectMatrix rMatrix) { - float lA; - float lB; - float lC; - float lD; - float lE; - float lF; - float rA; - float rB; - float rC; - float rD; - float rE; - float rF; - try { - lA = lMatrix.GetVariable("a").MustGetValueAsFloat(); - lB = lMatrix.GetVariable("b").MustGetValueAsFloat(); - lC = lMatrix.GetVariable("c").MustGetValueAsFloat(); - lD = lMatrix.GetVariable("d").MustGetValueAsFloat(); - lE = lMatrix.GetVariable("e").MustGetValueAsFloat(); - lF = lMatrix.GetVariable("f").MustGetValueAsFloat(); - rA = rMatrix.GetVariable("a").MustGetValueAsFloat(); - rB = rMatrix.GetVariable("b").MustGetValueAsFloat(); - rC = rMatrix.GetVariable("c").MustGetValueAsFloat(); - rD = rMatrix.GetVariable("d").MustGetValueAsFloat(); - rE = rMatrix.GetVariable("e").MustGetValueAsFloat(); - rF = rMatrix.GetVariable("f").MustGetValueAsFloat(); - } catch (InvalidCastException) { - throw new InvalidOperationException($"Invalid matrices '{lMatrix}' and '{rMatrix}' cannot be added."); - } - lMatrix.SetVariableValue("a", new DreamValue(lA + rA)); - lMatrix.SetVariableValue("b", new DreamValue(lB + rB)); - lMatrix.SetVariableValue("c", new DreamValue(lC + rC)); - lMatrix.SetVariableValue("d", new DreamValue(lD + rD)); - lMatrix.SetVariableValue("e", new DreamValue(lE + rE)); - lMatrix.SetVariableValue("f", new DreamValue(lF + rF)); + lMatrix.A += rMatrix.A; + lMatrix.B += rMatrix.B; + lMatrix.C += rMatrix.C; + lMatrix.D += rMatrix.D; + lMatrix.E += rMatrix.E; + lMatrix.F += rMatrix.F; } /// Subtracts the second given matrix from the first given matrix. - /// Note that this does use . - /// Thrown if either matrix has non-float members. public static void SubtractMatrix(DreamObjectMatrix lMatrix, DreamObjectMatrix rMatrix) { - float lA; - float lB; - float lC; - float lD; - float lE; - float lF; - float rA; - float rB; - float rC; - float rD; - float rE; - float rF; - try { - lA = lMatrix.GetVariable("a").MustGetValueAsFloat(); - lB = lMatrix.GetVariable("b").MustGetValueAsFloat(); - lC = lMatrix.GetVariable("c").MustGetValueAsFloat(); - lD = lMatrix.GetVariable("d").MustGetValueAsFloat(); - lE = lMatrix.GetVariable("e").MustGetValueAsFloat(); - lF = lMatrix.GetVariable("f").MustGetValueAsFloat(); - rA = rMatrix.GetVariable("a").MustGetValueAsFloat(); - rB = rMatrix.GetVariable("b").MustGetValueAsFloat(); - rC = rMatrix.GetVariable("c").MustGetValueAsFloat(); - rD = rMatrix.GetVariable("d").MustGetValueAsFloat(); - rE = rMatrix.GetVariable("e").MustGetValueAsFloat(); - rF = rMatrix.GetVariable("f").MustGetValueAsFloat(); - } catch (InvalidCastException) { - throw new InvalidOperationException($"Invalid matrices '{lMatrix}' and '{rMatrix}' cannot be subtracted."); - } - lMatrix.SetVariableValue("a", new DreamValue(lA - rA)); - lMatrix.SetVariableValue("b", new DreamValue(lB - rB)); - lMatrix.SetVariableValue("c", new DreamValue(lC - rC)); - lMatrix.SetVariableValue("d", new DreamValue(lD - rD)); - lMatrix.SetVariableValue("e", new DreamValue(lE - rE)); - lMatrix.SetVariableValue("f", new DreamValue(lF - rF)); + lMatrix.A -= rMatrix.A; + lMatrix.B -= rMatrix.B; + lMatrix.C -= rMatrix.C; + lMatrix.D -= rMatrix.D; + lMatrix.E -= rMatrix.E; + lMatrix.F -= rMatrix.F; } /// Multiplies the first given matrix by the other given matrix. - /// Note that this does use . - /// Thrown if either matrix has non-float members. public static void MultiplyMatrix(DreamObjectMatrix lMatrix, DreamObjectMatrix rMatrix) { - float lA; - float lB; - float lC; - float lD; - float lE; - float lF; - float rA; - float rB; - float rC; - float rD; - float rE; - float rF; - try { - lA = lMatrix.GetVariable("a").MustGetValueAsFloat(); - lB = lMatrix.GetVariable("b").MustGetValueAsFloat(); - lC = lMatrix.GetVariable("c").MustGetValueAsFloat(); - lD = lMatrix.GetVariable("d").MustGetValueAsFloat(); - lE = lMatrix.GetVariable("e").MustGetValueAsFloat(); - lF = lMatrix.GetVariable("f").MustGetValueAsFloat(); - rA = rMatrix.GetVariable("a").MustGetValueAsFloat(); - rB = rMatrix.GetVariable("b").MustGetValueAsFloat(); - rC = rMatrix.GetVariable("c").MustGetValueAsFloat(); - rD = rMatrix.GetVariable("d").MustGetValueAsFloat(); - rE = rMatrix.GetVariable("e").MustGetValueAsFloat(); - rF = rMatrix.GetVariable("f").MustGetValueAsFloat(); - } catch (InvalidCastException) { - throw new InvalidOperationException($"Invalid matrices '{lMatrix}' and '{rMatrix}' cannot be multiplied."); - } - lMatrix.SetVariableValue("a", new DreamValue(lA*rA + lD*rB)); - lMatrix.SetVariableValue("b", new DreamValue(lB*rA + lE*rB)); - lMatrix.SetVariableValue("c", new DreamValue(lC*rA + lF*rB + rC)); - lMatrix.SetVariableValue("d", new DreamValue(lA*rD + lD*rE)); - lMatrix.SetVariableValue("e", new DreamValue(lB*rD + lE*rE)); - lMatrix.SetVariableValue("f", new DreamValue(lC*rD + lF*rE + rF)); + float lA = lMatrix.A, lB = lMatrix.B, lC = lMatrix.C, lD = lMatrix.D, lE = lMatrix.E, lF = lMatrix.F; + float rA = rMatrix.A, rB = rMatrix.B, rC = rMatrix.C, rD = rMatrix.D, rE = rMatrix.E, rF = rMatrix.F; + + lMatrix.A = lA * rA + lD * rB; + lMatrix.B = lB * rA + lE * rB; + lMatrix.C = lC * rA + lF * rB + rC; + lMatrix.D = lA * rD + lD * rE; + lMatrix.E = lB * rD + lE * rE; + lMatrix.F = lC * rD + lF * rE + rF; } #endregion Helpers } diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectMob.cs b/OpenDreamRuntime/Objects/Types/DreamObjectMob.cs index ae5d9876d3..fabd364a06 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectMob.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectMob.cs @@ -38,6 +38,7 @@ public DreamObjectMob(DreamObjectDefinition objectDefinition) : base(objectDefin protected override bool TryGetVar(string varName, out DreamValue value) { switch (varName) { case "client": + Connection?.Client?.IncRef(); value = new(Connection?.Client); return true; case "key": @@ -60,10 +61,8 @@ protected override bool TryGetVar(string varName, out DreamValue value) { protected override void SetVar(string varName, DreamValue value) { switch (varName) { case "client": - value.TryGetValueAsDreamObject(out var newClient); - // An invalid client or a null does nothing here - if (newClient != null) { + if (value.TryGetValueAsDreamObject(out var newClient)) { newClient.Connection.Mob = this; } @@ -73,8 +72,7 @@ protected override void SetVar(string varName, DreamValue value) { case "key": case "ckey": if (!value.TryGetValueAsString(out Key)) { // TODO: Does the key get set to a player's un-canonized username? - if (Connection != null) - Connection.Mob = null; + Connection?.Mob = null; break; } diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs b/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs index 6919ffba0f..10e510dd54 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs @@ -51,19 +51,16 @@ public override void Initialize(DreamProcArguments args) { SetLoc(loc); //loc is set before /New() is ever called } - protected override void HandleDeletion(bool possiblyThreaded) { - // SAFETY: Deleting entities is not threadsafe. - if (possiblyThreaded) { - EnterIntoDelQueue(); - return; - } - + protected override void HandleDeletion() { + SetLoc(null); WalkManager.StopWalks(this); _particles?.Delete(); _particles = null; AtomManager.DeleteMovableEntity(this); - base.HandleDeletion(possiblyThreaded); + _contents.DecRef(); + _contents.Delete(); + base.HandleDeletion(); } protected override bool TryGetVar(string varName, out DreamValue value) { @@ -78,6 +75,7 @@ protected override bool TryGetVar(string varName, out DreamValue value) { value = new(Z); return true; case "loc": + Loc?.IncRef(); value = new(Loc); return true; case "bound_width": @@ -88,16 +86,18 @@ protected override bool TryGetVar(string varName, out DreamValue value) { value = (ScreenLoc != null) ? new(ScreenLoc) : DreamValue.Null; return true; case "contents": + _contents.IncRef(); value = new(_contents); return true; case "locs": - // Unimplemented; just return a list containing src.loc + // TODO: Unimplemented; just returns a list containing src.loc DreamList locs = ObjectTree.CreateList(); locs.AddValue(new(Loc)); value = new DreamValue(locs); return true; case "particles": + _particles?.IncRef(); value = new(_particles); return true; default: @@ -150,10 +150,13 @@ protected override void SetVar(string varName, DreamValue value) { break; _particles?.Owner = null; + particles.IncRef(); + _particles?.DecRef(); _particles = particles; _particles.Owner = this; } else { _particles?.Owner = null; + _particles?.DecRef(); _particles = null; } @@ -165,6 +168,10 @@ protected override void SetVar(string varName, DreamValue value) { } public void SetLoc(DreamObjectAtom? loc) { + var oldLoc = Loc; + + loc?.IncRef(); + Loc?.DecRef(); Loc = loc; if (TransformSystem == null) return; @@ -199,13 +206,19 @@ public void SetLoc(DreamObjectAtom? loc) { TransformSystem.SetWorldPosition(Entity, new Vector2(turf.X, turf.Y)); turf.Cell.Movables.Add(this); + if (oldLoc is null) + IncRef(); break; case DreamObjectMovable movable: TransformSystem.SetParent(Entity, movable.Entity); TransformSystem.SetLocalPosition(Entity, Vector2.Zero); + if (oldLoc is null) + IncRef(); break; case null: TransformSystem.SetParent(Entity, MapManager.GetMapEntityId(MapId.Nullspace)); + if (oldLoc is not null) + DecRef(); break; default: throw new ArgumentException($"Invalid loc {loc}"); diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs index 343f31cb2c..a42abc0ec5 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs @@ -29,13 +29,13 @@ public DreamObjectParticles(DreamObjectDefinition objectDefinition) : base(objec } } - protected override void HandleDeletion(bool possiblyThreaded) { + protected override void HandleDeletion() { Owner = null; _icons = null!; _iconStates = null!; _particlesComponent = null!; - base.HandleDeletion(possiblyThreaded); + base.HandleDeletion(); } protected override void SetVar(string varName, DreamValue value) { @@ -58,9 +58,12 @@ protected override void SetVar(string varName, DreamValue value) { break; case "bound1": //list or vector if (value.TryGetValueAsDreamList(out var bound1List) && bound1List.GetLength() >= 3) { - var boundX = bound1List.GetValue(new(1)).UnsafeGetValueAsFloat(); - var boundY = bound1List.GetValue(new(2)).UnsafeGetValueAsFloat(); - var boundZ = bound1List.GetValue(new(3)).UnsafeGetValueAsFloat(); + using var boundXValue = bound1List.GetValue(new(1)); + using var boundYValue = bound1List.GetValue(new(2)); + using var boundZValue = bound1List.GetValue(new(3)); + var boundX = boundXValue.UnsafeGetValueAsFloat(); + var boundY = boundYValue.UnsafeGetValueAsFloat(); + var boundZ = boundZValue.UnsafeGetValueAsFloat(); _particlesComponent.Bound1 = new Vector3(boundX, boundY, boundZ); } //else if vector @@ -68,9 +71,12 @@ protected override void SetVar(string varName, DreamValue value) { break; case "bound2": //list or vector if (value.TryGetValueAsDreamList(out var bound2List) && bound2List.GetLength() >= 3) { - var boundX = bound2List.GetValue(new(1)).UnsafeGetValueAsFloat(); - var boundY = bound2List.GetValue(new(2)).UnsafeGetValueAsFloat(); - var boundZ = bound2List.GetValue(new(3)).UnsafeGetValueAsFloat(); + using var boundXValue = bound2List.GetValue(new(1)); + using var boundYValue = bound2List.GetValue(new(2)); + using var boundZValue = bound2List.GetValue(new(3)); + var boundX = boundXValue.UnsafeGetValueAsFloat(); + var boundY = boundYValue.UnsafeGetValueAsFloat(); + var boundZ = boundZValue.UnsafeGetValueAsFloat(); _particlesComponent.Bound2 = new Vector3(boundX, boundY, boundZ); } //else if vector @@ -78,9 +84,12 @@ protected override void SetVar(string varName, DreamValue value) { break; case "gravity": //list or vector if (value.TryGetValueAsDreamList(out var gravityList) && gravityList.GetLength() >= 3) { - var gravityX = gravityList.GetValue(new(1)).UnsafeGetValueAsFloat(); - var gravityY = gravityList.GetValue(new(2)).UnsafeGetValueAsFloat(); - var gravityZ = gravityList.GetValue(new(3)).UnsafeGetValueAsFloat(); + using var gravityXValue = gravityList.GetValue(new(1)); + using var gravityYValue = gravityList.GetValue(new(2)); + using var gravityZValue = gravityList.GetValue(new(3)); + var gravityX = gravityXValue.UnsafeGetValueAsFloat(); + var gravityY = gravityYValue.UnsafeGetValueAsFloat(); + var gravityZ = gravityZValue.UnsafeGetValueAsFloat(); _particlesComponent.Gravity = new Vector3(gravityX, gravityY, gravityZ); } //else if vector @@ -193,6 +202,7 @@ protected override void SetVar(string varName, DreamValue value) { _particlesComponent.SpawnPosition = generator.RequireType(); } else if (DreamObjectVector.TryCreateFromValue(value, ObjectTree, out var vector)) { _particlesComponent.SpawnPosition = new GeneratorVector2(vector.AsVector2); + vector.DecRef(); } else { _particlesComponent.SpawnPosition = new GeneratorVector2(Vector2.Zero); } @@ -205,6 +215,7 @@ protected override void SetVar(string varName, DreamValue value) { _particlesComponent.SpawnVelocity = generator.RequireType(); } else if (DreamObjectVector.TryCreateFromValue(value, ObjectTree, out var vector)) { _particlesComponent.SpawnVelocity = new GeneratorVector2(vector.AsVector2); + vector.DecRef(); } else { _particlesComponent.SpawnVelocity = new GeneratorVector2(Vector2.Zero); } @@ -217,6 +228,7 @@ protected override void SetVar(string varName, DreamValue value) { _particlesComponent.Scale = generator.RequireType(); } else if (DreamObjectVector.TryCreateFromValue(value, ObjectTree, out var vector)) { _particlesComponent.Scale = new GeneratorVector2(vector.AsVector2); + vector.DecRef(); } else { _particlesComponent.Scale = new GeneratorVector2(Vector2.One); } @@ -229,6 +241,7 @@ protected override void SetVar(string varName, DreamValue value) { _particlesComponent.Growth = generator.RequireType(); } else if (DreamObjectVector.TryCreateFromValue(value, ObjectTree, out var vector)) { _particlesComponent.Growth = new GeneratorVector2(vector.AsVector2); + vector.DecRef(); } else { _particlesComponent.Growth = new GeneratorVector2(Vector2.Zero); } @@ -257,6 +270,7 @@ protected override void SetVar(string varName, DreamValue value) { _particlesComponent.Friction = generator.RequireType(); } else if (DreamObjectVector.TryCreateFromValue(value, ObjectTree, out var vector)) { _particlesComponent.Friction = new GeneratorVector2(vector.AsVector2); + vector.DecRef(); } else { _particlesComponent.Friction = new GeneratorVector2(Vector2.Zero); } @@ -269,6 +283,7 @@ protected override void SetVar(string varName, DreamValue value) { _particlesComponent.Drift = generator.RequireType(); } else if (DreamObjectVector.TryCreateFromValue(value, ObjectTree, out var vector)) { _particlesComponent.Drift = new GeneratorVector2(vector.AsVector2); + vector.DecRef(); } else { _particlesComponent.Drift = new GeneratorVector2(Vector2.Zero); } diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectRegex.cs b/OpenDreamRuntime/Objects/Types/DreamObjectRegex.cs index f1364b1e3e..ae17f1f233 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectRegex.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectRegex.cs @@ -93,6 +93,7 @@ public DreamValue FindHelper(string haystackString, int start, int end) { } SetVariable("group", new DreamValue(groupList)); + groupList.DecRef(); } if (IsGlobal) { diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectSavefile.cs b/OpenDreamRuntime/Objects/Types/DreamObjectSavefile.cs index 7de70ccfa5..dbc8c62b34 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectSavefile.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectSavefile.cs @@ -4,6 +4,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using DMCompiler; +using JetBrains.Annotations; using OpenDreamRuntime.Procs; using OpenDreamRuntime.Resources; @@ -122,19 +123,13 @@ public override void Initialize(DreamProcArguments args) { Savefiles.Add(this); } - protected override void HandleDeletion(bool possiblyThreaded) { - // SAFETY: Close() is not threadsafe and doesn't have reason to be. - if (possiblyThreaded) { - EnterIntoDelQueue(); - return; - } - + protected override void HandleDeletion() { if (Resource is null) return; // Seemingly a long-standing issue, I don't know how to fix this, and it only now appears due to the fact objects always Del() now. // Why we can get here? who knows lol Close(); - base.HandleDeletion(possiblyThreaded); + base.HandleDeletion(); } protected override bool TryGetVar(string varName, out DreamValue value) { @@ -217,6 +212,7 @@ public override void OperatorOutput(DreamValue value) { SetSavefileValue(null, value); } + [MustDisposeResource] public DreamValue OperatorInput() { _eof = true; return GetSavefileValue(null); @@ -294,6 +290,7 @@ private SfDreamJsonValue SeekTo(string to, bool createPath=false) { return tempDir; } + [MustDisposeResource] public DreamValue GetSavefileValue(string? index) { if (index == null) { return DeserializeJsonValue(CurrentDir); @@ -366,6 +363,7 @@ public void SetSavefileValue(string? index, DreamValue value) { /// /// Turn the json magic value into real DM values /// + [MustDisposeResource] public DreamValue DeserializeJsonValue(SfDreamJsonValue value) { switch (value) { case SfDreamFileValue sfDreamFileValue: @@ -373,10 +371,15 @@ public DreamValue DeserializeJsonValue(SfDreamJsonValue value) { case SfDreamListValue sfDreamListValue: var l = ObjectTree.CreateList(); for (int i=0; i < sfDreamListValue.AssocKeys.Count; i++) { - if (sfDreamListValue.AssocData?[i] != null) //note that null != DreamValue.Null - l.SetValue(DeserializeJsonValue(sfDreamListValue.AssocKeys[i]), DeserializeJsonValue(sfDreamListValue.AssocData[i]!)); - else - l.AddValue(DeserializeJsonValue(sfDreamListValue.AssocKeys[i])); + using var sfValue = DeserializeJsonValue(sfDreamListValue.AssocKeys[i]); + + if (sfDreamListValue.AssocData?[i] != null) { //note that null != DreamValue.Null + using var sfAssocValue = DeserializeJsonValue(sfDreamListValue.AssocData[i]!); + + l.SetValue(sfValue, sfAssocValue); + } else { + l.AddValue(sfValue); + } } return new DreamValue(l); @@ -389,17 +392,24 @@ public DreamValue DeserializeJsonValue(SfDreamJsonValue value) { else break; - if (storedObjectVars.TryGetValue("type", out SfDreamJsonValue? storedObjectTypeJson) && DeserializeJsonValue(storedObjectTypeJson).TryGetValueAsType(out TreeEntry? objectTypeActual)) { - DreamObject resultObj = ObjectTree.CreateObject(objectTypeActual); - foreach (string key in storedObjectVars.Keys) { - if (key == "type" || storedObjectVars[key] is SfDreamDir) //is type or a non-valued dir - continue; - resultObj.SetVariable(key, DeserializeJsonValue(storedObjectVars[key])); - } + if (storedObjectVars.TryGetValue("type", out SfDreamJsonValue? storedObjectTypeJson)) { + using var storedObject = DeserializeJsonValue(storedObjectTypeJson); + + if (storedObject.TryGetValueAsType(out TreeEntry? objectTypeActual)) { + var resultObj = ObjectTree.CreateObject(objectTypeActual); - resultObj.InitSpawn(new DreamProcArguments()); - resultObj.SpawnProc("Read", null, new DreamValue(this)); - return new DreamValue(resultObj); + foreach (string key in storedObjectVars.Keys) { + if (key == "type" || storedObjectVars[key] is SfDreamDir) //is type or a non-valued dir + continue; + + using var varValue = DeserializeJsonValue(storedObjectVars[key]); + resultObj.SetVariable(key, varValue); + } + + resultObj.InitSpawn(new DreamProcArguments()); + resultObj.SpawnProc("Read", null, new DreamValue(this)).Dispose(); + return new DreamValue(resultObj); + } } throw new InvalidDataException("Unable to deserialize object in savefile: " + ((storedObjectTypeJson as SfDreamType) is null ? "no type specified (corrupted savefile?)" : "invalid type "+((SfDreamType)storedObjectTypeJson).TypePath)); @@ -463,7 +473,8 @@ public SfDreamJsonValue SerializeDreamValue(DreamValue val, int objectCount = 0) if (!dreamList.ContainsKey(keyValue)) { jsonEncodedList.AssocData!.Add(null); //store an actual null if this key does not have an associated value - this is distinct from storing DreamValue.Null } else { - var assocValue = dreamList.GetValue(keyValue); + using var assocValue = dreamList.GetValue(keyValue); + if (assocValue.TryGetValueAsDreamObject(out _) && !assocValue.IsNull) { SfDreamJsonValue jsonEncodedObject = SerializeDreamValue(assocValue, thisObjectCount); //merge the object subdirectories into the list parent directory @@ -510,7 +521,7 @@ public SfDreamJsonValue SerializeDreamValue(DreamValue val, int objectCount = 0) if((dreamObject.ObjectDefinition.ConstVariables is not null && dreamObject.ObjectDefinition.ConstVariables.Contains(key)) || (dreamObject.ObjectDefinition.TmpVariables is not null && dreamObject.ObjectDefinition.TmpVariables.Contains(key))) continue; //skip const & tmp variables (they're not saved) - DreamValue objectVarVal = dreamObject.GetVariable(key); + using var objectVarVal = dreamObject.GetVariable(key); if (dreamObject.ObjectDefinition.Variables[key] == objectVarVal || (objectVarVal.TryGetValueAsDreamObject(out DreamObject? equivTestObject) && equivTestObject != null && equivTestObject.OperatorEquivalent(dreamObject.ObjectDefinition.Variables[key]).IsTruthy())) continue; //skip default values - equivalence check used for lists and objects @@ -528,7 +539,7 @@ public SfDreamJsonValue SerializeDreamValue(DreamValue val, int objectCount = 0) } //Call the Write proc on the object - note that this is a weird one, it does not need to call parent to the native function to save the object - dreamObject.SpawnProc("Write", null, new DreamValue(this)); + dreamObject.SpawnProc("Write", null, new DreamValue(this)).Dispose(); jsonEncodedObject[jsonEncodedObject.Path] = objectVars; return jsonEncodedObject; } diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectSound.cs b/OpenDreamRuntime/Objects/Types/DreamObjectSound.cs index 9757585270..960b70c587 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectSound.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectSound.cs @@ -1,7 +1,56 @@ namespace OpenDreamRuntime.Objects.Types; public sealed class DreamObjectSound : DreamObject { + public ushort Channel, Volume; + public float Offset; + public byte Repeat { get; set => field = Math.Clamp(value, (byte)0, (byte)2); } + public DreamValue File; + public DreamObjectSound(DreamObjectDefinition objectDefinition) : base(objectDefinition) { + if (objectDefinition.TryGetVariable("channel", out var channel)) { + channel.TryGetValueAsInteger(out var channelValue); + Channel = (ushort)channelValue; + } + + if (objectDefinition.TryGetVariable("volume", out var volume)) { + volume.TryGetValueAsInteger(out var volumeValue); + Volume = (ushort)volumeValue; + } + + if (objectDefinition.TryGetVariable("offset", out var offset)) { + offset.TryGetValueAsFloat(out Offset); + } + + if (objectDefinition.TryGetVariable("repeat", out var repeat)) { + Repeat = (byte)repeat.UnsafeGetValueAsFloat(); + } + + objectDefinition.TryGetVariable("file", out File); + } + + protected override bool TryGetVar(string varName, out DreamValue value) { + switch (varName) { + case "channel": value = new(Channel); return true; + case "volume": value = new(Volume); return true; + case "offset": value = new(Offset); return true; + case "repeat": value = new(Repeat); return true; + case "file": File.IncRef(); value = File; return true; + default: return base.TryGetVar(varName, out value); + } + } + protected override void SetVar(string varName, DreamValue value) { + switch (varName) { + case "channel": Channel = (ushort)value.UnsafeGetValueAsFloat(); break; + case "volume": Volume = (ushort)value.UnsafeGetValueAsFloat(); break; + case "offset": Offset = value.UnsafeGetValueAsFloat(); break; + case "repeat": Repeat = (byte)value.UnsafeGetValueAsFloat(); break; + case "file": + value.IncRef(); + File.DecRef(); + File = value; + break; + default: base.SetVar(varName, value); break; + } } } diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectTurf.cs b/OpenDreamRuntime/Objects/Types/DreamObjectTurf.cs index 6f27f5630f..e6eba2d6bd 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectTurf.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectTurf.cs @@ -1,4 +1,5 @@ using OpenDreamRuntime.Map; +using OpenDreamRuntime.Procs; using OpenDreamShared.Dream; namespace OpenDreamRuntime.Objects.Types; @@ -8,8 +9,7 @@ public sealed class DreamObjectTurf : DreamObjectAtom { public readonly TurfContentsList Contents; public ImmutableAppearance Appearance; public IDreamMapManager.Cell Cell; - - public bool IsDense => GetVariable("density").IsTruthy(); + public bool IsDense; public DreamObjectTurf(DreamObjectDefinition objectDefinition, int x, int y, int z) : base(objectDefinition) { X = x; @@ -21,12 +21,25 @@ public DreamObjectTurf(DreamObjectDefinition objectDefinition, int x, int y, int Appearance = AppearanceSystem!.AddAppearance(AtomManager.GetAppearanceFromDefinition(ObjectDefinition)); } + public override void Initialize(DreamProcArguments args) { + base.Initialize(args); + + ObjectDefinition.TryGetVariable("density", out var density); + IsDense = density.IsTruthy(); + } + public void SetTurfType(DreamObjectDefinition objectDefinition) { if (!objectDefinition.IsSubtypeOf(ObjectTree.Turf)) throw new Exception($"Cannot set turf's type to {objectDefinition.Type}"); ObjectDefinition = objectDefinition; - Variables?.Clear(); + + if (Variables != null) { + foreach (var varValue in Variables.Values) + varValue.DecRef(); + + Variables?.Clear(); + } Initialize(new()); } @@ -41,6 +54,12 @@ public void OnAreaChange(DreamObjectArea oldArea) { DreamMapManager.SetTurfAppearance(this, newAppearance); } + protected override void HandleDeletion() { + Contents.DecRef(); + Contents.Delete(); + base.HandleDeletion(); + } + protected override bool TryGetVar(string varName, out DreamValue value) { switch (varName) { case "x": @@ -53,9 +72,14 @@ protected override bool TryGetVar(string varName, out DreamValue value) { value = new(Z); return true; case "loc": + Cell.Area.IncRef(); value = new(Cell.Area); return true; + case "density": + value = IsDense ? DreamValue.True : DreamValue.False; + return true; case "contents": + Contents.IncRef(); value = new(Contents); return true; default: @@ -65,6 +89,9 @@ protected override bool TryGetVar(string varName, out DreamValue value) { protected override void SetVar(string varName, DreamValue value) { switch (varName) { + case "density": + IsDense = value.IsTruthy(); + break; case "contents": Contents.Cut(); diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectVector.cs b/OpenDreamRuntime/Objects/Types/DreamObjectVector.cs index 22f18e3f4f..cf695d3acc 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectVector.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectVector.cs @@ -147,11 +147,13 @@ public override DreamValue OperatorMultiplyRef(DreamValue b, DMProcState state) X *= scalar; Y *= scalar; Z *= scalar; + IncRef(); return new DreamValue(this); } else if (b.TryGetValueAsDreamObject(out var right)) { X *= right.X; Y *= right.Y; Z *= right.Z; + IncRef(); return new DreamValue(this); } @@ -192,6 +194,7 @@ public override DreamValue OperatorDivideRef(DreamValue b, DMProcState state) { X /= scalar; Y /= scalar; Z /= scalar; + IncRef(); return new DreamValue(this); } else if (b.TryGetValueAsDreamObject(out var right)) { if (right.X == 0 || right.Y == 0 || (Is3D && right.Z == 0)) @@ -199,6 +202,7 @@ public override DreamValue OperatorDivideRef(DreamValue b, DMProcState state) { X /= right.X; Y /= right.Y; Z = right.Z == 0 ? 0 : Z / right.Z; + IncRef(); return new DreamValue(this); } @@ -212,6 +216,7 @@ public override DreamValue OperatorAppend(DreamValue b) { Z += right.Z; Is3D = Is3D || right.Is3D; + IncRef(); return new DreamValue(this); } @@ -225,6 +230,7 @@ public override DreamValue OperatorRemove(DreamValue b) { Z -= right.Z; Is3D = Is3D || right.Is3D; + IncRef(); return new DreamValue(this); } @@ -303,9 +309,9 @@ public static bool TryCreateFromValue(DreamValue value, DreamObjectTree tree, [N var length = list.GetLength(); if (length >= 3) { - var x = list.GetValue(new(1)); - var y = list.GetValue(new(2)); - var z = list.GetValue(new(3)); + using var x = list.GetValue(new(1)); + using var y = list.GetValue(new(2)); + using var z = list.GetValue(new(3)); vector = tree.CreateObject(tree.Vector); vector.Initialize(new(x, y, z)); @@ -313,8 +319,8 @@ public static bool TryCreateFromValue(DreamValue value, DreamObjectTree tree, [N } if (length == 2) { - var x = list.GetValue(new(1)); - var y = list.GetValue(new(2)); + using var x = list.GetValue(new(1)); + using var y = list.GetValue(new(2)); vector = tree.CreateObject(tree.Vector); vector.Initialize(new(x, y)); @@ -356,6 +362,10 @@ public static DreamObjectVector CreateFromValue(Vector2 value, DreamObjectTree t return vector; } + public override string ToString() { + return Is3D ? $"vector({X},{Y},{Z})" : $"vector({X},{Y})"; + } + // TODO: Operators, supports indexing and "most math" // TODO: For loop support } diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs b/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs index 346c3be4db..4448fb8a1e 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs @@ -22,6 +22,8 @@ public sealed class DreamObjectWorld : DreamObject { public float TickUsage => (Environment.TickCount64 - DreamManager.CurrentTickStart) / (float)(_gameTiming.TickPeriod.TotalMilliseconds) * 100; + public int TimeOfDay => (int)DateTime.UtcNow.TimeOfDay.TotalMilliseconds / 100; + public float Cpu { get; set; } public readonly int IconSize; @@ -67,8 +69,7 @@ private bool DisplayIPv6 { /// Tries to return the address of the server, as it appears over the internet. May return null. private IPAddress? InternetAddress => null; //TODO: Implement this! - public DreamObjectWorld(DreamObjectDefinition objectDefinition) : - base(objectDefinition) { + public DreamObjectWorld(DreamObjectDefinition objectDefinition) : base(objectDefinition) { IoCManager.InjectDependencies(this); SetTicklag(objectDefinition.Variables["tick_lag"]); @@ -101,17 +102,16 @@ public DreamObjectWorld(DreamObjectDefinition objectDefinition) : new DreamValue(ObjectTree.CreateList()); } - protected override void HandleDeletion(bool possiblyThreaded) { - // SAFETY: Server shutdown is, spoiler, not threadsafe. - if (possiblyThreaded) { - EnterIntoDelQueue(); - return; - } + // TODO: Respect /world/Delete() not calling parent and aborting shutdown + protected override void HandleDeletion() { + // TODO: Delete every atom + + _params.DecRef(); // There shouldn't be two instances of world, but to be safe var isServerWorld = (this == DreamManager.WorldInstance); - base.HandleDeletion(possiblyThreaded); + base.HandleDeletion(); if (isServerWorld) _server.Shutdown("world was deleted"); } @@ -123,6 +123,7 @@ protected override bool TryGetVar(string varName, out DreamValue value) { return true; case "params": + _params.IncRef(); value = _params; return true; @@ -148,7 +149,7 @@ protected override bool TryGetVar(string varName, out DreamValue value) { return true; case "timeofday": - value = new DreamValue((int)DateTime.UtcNow.TimeOfDay.TotalMilliseconds / 100); + value = new DreamValue(TimeOfDay); return true; case "timezone": diff --git a/OpenDreamRuntime/Objects/Types/IDreamList.cs b/OpenDreamRuntime/Objects/Types/IDreamList.cs index 731ff826b4..4ffa15fb30 100644 --- a/OpenDreamRuntime/Objects/Types/IDreamList.cs +++ b/OpenDreamRuntime/Objects/Types/IDreamList.cs @@ -1,4 +1,5 @@ using System.Linq; +using JetBrains.Annotations; namespace OpenDreamRuntime.Objects.Types; @@ -6,7 +7,7 @@ public interface IDreamList { public bool IsAssociative { get; } public void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false); - public DreamValue GetValue(DreamValue key); + [MustDisposeResource] public DreamValue GetValue(DreamValue key); public bool ContainsKey(DreamValue key); public IEnumerable EnumerateValues(); public int GetLength(); diff --git a/OpenDreamRuntime/Procs/AsyncNativeProc.cs b/OpenDreamRuntime/Procs/AsyncNativeProc.cs index d784f79e2a..d76a48de3a 100644 --- a/OpenDreamRuntime/Procs/AsyncNativeProc.cs +++ b/OpenDreamRuntime/Procs/AsyncNativeProc.cs @@ -2,6 +2,7 @@ using System.Text; using System.Threading.Tasks; using DMCompiler.DM; +using JetBrains.Annotations; using OpenDreamRuntime.Objects; using OpenDreamRuntime.Resources; using Dependency = Robust.Shared.IoC.DependencyAttribute; @@ -49,15 +50,19 @@ public AsyncNativeProcState() { IoCManager.InjectDependencies(this); } - public void Initialize(AsyncNativeProc? proc, Func> taskFunc, DreamThread thread, DreamObject? src, DreamObject? usr, DreamProcArguments arguments) { + public void Initialize(AsyncNativeProc? proc, Func> taskFunc, DreamThread thread, DreamObject? src, DreamObject? usr, [HandlesResourceDisposal] DreamProcArguments arguments) { base.Initialize(thread, true); + arguments.Values.CopyTo(_arguments); + foreach (var arg in _arguments) + arg.IncRef(); + _proc = proc; _taskFunc = taskFunc; Instance = src; Usr = usr; - arguments.Values.CopyTo(_arguments); ArgumentCount = arguments.Count; + arguments.Dispose(); } // Used to avoid reentrant resumptions in our proc @@ -66,7 +71,7 @@ public void SafeResume() { return; } - Thread.Resume(); + Thread.Resume().Dispose(); } public Task Call(DreamProc proc, DreamObject? src, DreamObject? usr, params DreamValue[] arguments) { @@ -102,6 +107,9 @@ public override void Cancel() { public override void Dispose() { base.Dispose(); + for (int i = 0; i < ArgumentCount; i++) + _arguments[i].Dispose(); + Instance = null!; Usr = null!; ArgumentCount = 0; @@ -188,7 +196,7 @@ public override void SetArgument(int id, DreamValue value) { private readonly Dictionary? _defaultArgumentValues = defaultArgumentValues; - public override ProcState CreateState(DreamThread thread, DreamObject? src, DreamObject? usr, DreamProcArguments arguments) { + public override ProcState CreateState(DreamThread thread, DreamObject? src, DreamObject? usr, [HandlesResourceDisposal] DreamProcArguments arguments) { if (!AsyncNativeProcState.Pool.TryPop(out var state)) { state = new AsyncNativeProcState(); } diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.CallExt.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.CallExt.cs index b7d153391f..ee7e7e8274 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.CallExt.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.CallExt.cs @@ -1,5 +1,6 @@ using System.Runtime.InteropServices; using DMCompiler.Bytecode; +using JetBrains.Annotations; using Api = OpenDreamRuntime.ByondApi.ByondApi; namespace OpenDreamRuntime.Procs; @@ -12,7 +13,7 @@ private static ProcStatus CallExt( if(!source.TryGetValueAsString(out var dllName)) throw new Exception($"{source} is not a valid DLL"); - var popProc = state.Pop(); + using var popProc = state.Pop(); if(!popProc.TryGetValueAsString(out var procName)) { throw new Exception($"{popProc} is not a valid proc name"); } @@ -35,7 +36,7 @@ private static unsafe ProcStatus CallExtByond( DMProcState state, string dllName, string procName, - DreamProcArguments arguments) { + [HandlesResourceDisposal] DreamProcArguments arguments) { // TODO: Don't allocate string copy // TODO: Handle stdcall (do we care?) var entryPoint = (delegate* unmanaged[Cdecl]) @@ -49,9 +50,9 @@ private static unsafe ProcStatus CallExtByond( args[i] = Api.ValueToByondApi(arg); } - var result = Api.DoCall(entryPoint, args); - - state.Push(Api.ValueFromDreamApi(result)); + using var result = Api.ValueFromDreamApi(Api.DoCall(entryPoint, args)); + state.Push(result); + arguments.Dispose(); return ProcStatus.Continue; } @@ -59,7 +60,7 @@ private static unsafe ProcStatus CallExtString( DMProcState state, string dllName, string procName, - DreamProcArguments arguments) { + [HandlesResourceDisposal] DreamProcArguments arguments) { var entryPoint = DllHelper.ResolveDllTarget(state.Proc.DreamResourceManager, dllName, procName); Span argV = stackalloc nint[arguments.Count]; @@ -67,6 +68,7 @@ private static unsafe ProcStatus CallExtString( try { for (var i = 0; i < argV.Length; i++) { var arg = arguments.GetArgument(i).Stringify(); + argV[i] = Marshal.StringToCoTaskMemUTF8(arg); } @@ -88,6 +90,7 @@ private static unsafe ProcStatus CallExtString( state.Push(new DreamValue(retString)); return ProcStatus.Continue; } finally { + arguments.Dispose(); foreach (var arg in argV) { if (arg != 0) Marshal.ZeroFreeCoTaskMemUTF8(arg); diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index 5eaa479384..467b013295 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using DMCompiler; using DMCompiler.Bytecode; +using JetBrains.Annotations; using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.Types; using OpenDreamRuntime.Procs.Native; @@ -21,15 +22,16 @@ internal static partial class DMOpcodeHandlers { #region Values public static ProcStatus PushReferenceValue(DMProcState state) { - DreamReference reference = state.ReadReference(); + var reference = state.ReadReference(); + using var value = state.GetReferenceValue(reference); - state.Push(state.GetReferenceValue(reference)); + state.Push(value); return ProcStatus.Continue; } public static ProcStatus Assign(DMProcState state) { - DreamReference reference = state.ReadReference(); - DreamValue value = state.Pop(); + var reference = state.ReadReference(); + using var value = state.Pop(); state.AssignReference(reference, value); state.Push(value); @@ -37,9 +39,9 @@ public static ProcStatus Assign(DMProcState state) { } public static ProcStatus AssignInto(DMProcState state) { - DreamReference reference = state.ReadReference(); - DreamValue value = state.Pop(); - DreamValue first = state.GetReferenceValue(reference); + var reference = state.ReadReference(); + using var value = state.Pop(); + using var first = state.GetReferenceValue(reference); //TODO call operator:= for DreamObjects state.AssignReference(reference, value); @@ -54,9 +56,11 @@ public static ProcStatus CreateList(DMProcState state) { foreach (DreamValue value in state.PopCount(size)) { list.AddValue(value); + value.Dispose(); } state.Push(new DreamValue(list)); + list.DecRef(); return ProcStatus.Continue; } @@ -66,42 +70,51 @@ public static ProcStatus CreateMultidimensionalList(DMProcState state) { var dimensionSizes = state.PopCount(dimensionCount); // Same as new /list(1, 2, 3) - list.Initialize(new(dimensionSizes)); + using (var listInitArgs = new DreamProcArguments(dimensionSizes)) // Needs disposed of before we modify the stack again with Push() + list.Initialize(listInitArgs); + foreach (var value in dimensionSizes) + value.Dispose(); + state.Push(new DreamValue(list)); + list.DecRef(); return ProcStatus.Continue; } public static ProcStatus CreateAssociativeList(DMProcState state) { - int size = state.ReadInt(); + var size = state.ReadInt(); var list = state.Proc.ObjectTree.CreateList(size); + var popped = state.PopCount(size * 2); - ReadOnlySpan popped = state.PopCount(size * 2); for (int i = 0; i < popped.Length; i += 2) { - DreamValue key = popped[i]; + using var key = popped[i]; + using var value = popped[i + 1]; if (key.IsNull) { - list.AddValue(popped[i + 1]); + list.AddValue(value); } else { - list.SetValue(key, popped[i + 1], allowGrowth: true); + list.SetValue(key, value, allowGrowth: true); } } state.Push(new DreamValue(list)); + list.DecRef(); return ProcStatus.Continue; } public static ProcStatus CreateStrictAssociativeList(DMProcState state) { - int size = state.ReadInt(); + var size = state.ReadInt(); var list = state.Proc.ObjectTree.CreateAssocList(size); + var popped = state.PopCount(size * 2); - ReadOnlySpan popped = state.PopCount(size * 2); for (int i = 0; i < popped.Length; i += 2) { - DreamValue key = popped[i]; + using var key = popped[i]; + using var value = popped[i + 1]; - list.SetValue(key, popped[i + 1], allowGrowth: true); + list.SetValue(key, value, allowGrowth: true); } state.Push(new DreamValue(list)); + list.DecRef(); return ProcStatus.Continue; } @@ -112,7 +125,9 @@ private static IDreamValueEnumerator GetContentsEnumerator(AtomManager atomManag return new DreamValueArrayEnumerator([], null); if (dreamObject is DreamObjectAtom) { - list = dreamObject.GetVariable("contents").MustGetValueAsDreamList(); + using var contents = dreamObject.GetVariable("contents"); + + list = contents.MustGetValueAsDreamList(); } else if (dreamObject is DreamObjectWorld) { return new WorldContentsEnumerator(atomManager, filterType); } @@ -126,6 +141,11 @@ private static IDreamValueEnumerator GetContentsEnumerator(AtomManager atomManag var values = list.CopyToArray(); var assocValues = list.IsAssociative ? list.CopyAssocValues() : null; + foreach (var copiedValue in values) + copiedValue.IncRef(); + if (assocValues != null) + foreach (var copiedAssocValue in assocValues.Values) + copiedAssocValue.IncRef(); return filterType == null ? new DreamValueArrayEnumerator(values, assocValues) @@ -138,7 +158,8 @@ private static IDreamValueEnumerator GetContentsEnumerator(AtomManager atomManag public static ProcStatus CreateListEnumerator(DMProcState state) { var enumeratorId = state.ReadInt(); - var enumerator = GetContentsEnumerator(state.Proc.AtomManager, state.Pop(), null); + using var list = state.Pop(); + var enumerator = GetContentsEnumerator(state.Proc.AtomManager, list, null); state.Enumerators[enumeratorId] = enumerator; return ProcStatus.Continue; @@ -148,7 +169,8 @@ public static ProcStatus CreateFilteredListEnumerator(DMProcState state) { var enumeratorId = state.ReadInt(); var filterTypeId = state.ReadInt(); var filterType = state.Proc.ObjectTree.GetTreeEntry(filterTypeId); - var enumerator = GetContentsEnumerator(state.Proc.AtomManager, state.Pop(), filterType); + using var list = state.Pop(); + var enumerator = GetContentsEnumerator(state.Proc.AtomManager, list, filterType); state.Enumerators[enumeratorId] = enumerator; return ProcStatus.Continue; @@ -156,7 +178,7 @@ public static ProcStatus CreateFilteredListEnumerator(DMProcState state) { public static ProcStatus CreateTypeEnumerator(DMProcState state) { var enumeratorId = state.ReadInt(); - var typeValue = state.Pop(); + using var typeValue = state.Pop(); if (!typeValue.TryGetValueAsType(out var type)) { throw new Exception($"Cannot create a type enumerator with type {typeValue}"); } @@ -183,9 +205,9 @@ public static ProcStatus CreateTypeEnumerator(DMProcState state) { public static ProcStatus CreateRangeEnumerator(DMProcState state) { var enumeratorId = state.ReadInt(); - DreamValue step = state.Pop(); - DreamValue rangeEnd = state.Pop(); - DreamValue rangeStart = state.Pop(); + using var step = state.Pop(); + using var rangeEnd = state.Pop(); + using var rangeStart = state.Pop(); if (!step.TryGetValueAsFloat(out var stepValue)) throw new Exception($"Invalid step {step}, must be a number"); @@ -200,9 +222,10 @@ public static ProcStatus CreateRangeEnumerator(DMProcState state) { public static ProcStatus CreateObject(DMProcState state) { var argumentInfo = state.ReadProcArguments(); - var val = state.Pop(); + using var val = state.Pop(); + using var overridesVal = state.Pop(); Dictionary? overrides = null; - if (state.Pop().TryGetValueAsString(out var jsonDict)) { + if (overridesVal.TryGetValueAsString(out var jsonDict)) { overrides = JsonSerializer.Deserialize>(jsonDict); } @@ -213,7 +236,7 @@ public static ProcStatus CreateObject(DMProcState state) { } } else if (val.TryGetValueAsProc(out var proc)) { // new /proc/proc_name(Destination,Name,Desc) - var arguments = state.PopProcArguments(null, argumentInfo.Type, argumentInfo.StackSize); + using var arguments = state.PopProcArguments(null, argumentInfo.Type, argumentInfo.StackSize); var destination = arguments.GetArgument(0); // TODO: Name and Desc arguments @@ -247,9 +270,10 @@ public static ProcStatus CreateObject(DMProcState state) { state.Proc.DreamMapManager.SetTurf(turf, objectDef, newArguments); if (overrides is not null) { - foreach (KeyValuePair varOverride in overrides) { - turf.SetVariable(varOverride.Key, - state.Proc.ObjectTree.GetDreamValueFromJsonElement(varOverride.Value)); + foreach (var varOverride in overrides) { + using var overrideValue = state.Proc.ObjectTree.GetDreamValueFromJsonElement(varOverride.Value); + + turf.SetVariable(varOverride.Key, overrideValue); } } @@ -259,14 +283,17 @@ public static ProcStatus CreateObject(DMProcState state) { var newObject = state.Proc.ObjectTree.CreateObject(objectType); if (overrides is not null) { - foreach (KeyValuePair varOverride in overrides) { - newObject.SetVariable(varOverride.Key, state.Proc.ObjectTree.GetDreamValueFromJsonElement(varOverride.Value)); + foreach (var varOverride in overrides) { + using var overrideValue = state.Proc.ObjectTree.GetDreamValueFromJsonElement(varOverride.Value); + + newObject.SetVariable(varOverride.Key, overrideValue); } } var s = newObject.InitProc(state.Thread, state.Usr, newArguments); state.Thread.PushProcState(s); + newObject.DecRef(); return ProcStatus.Called; } @@ -288,6 +315,7 @@ private static void ThrowCannotCreateUnknownObject(DreamValue val) { public static ProcStatus DestroyEnumerator(DMProcState state) { var enumeratorId = state.ReadInt(); + state.Enumerators[enumeratorId]?.Dispose(); state.Enumerators[enumeratorId] = null; return ProcStatus.Continue; } @@ -413,7 +441,7 @@ public static ProcStatus FormatString(DMProcState state) { FormatSuffix? postPrefix = null; // Prefix that needs the effects of a suffix - ReadOnlySpan interps = state.PopCount(interpCount); + var interps = state.PopCount(interpCount); int nextInterpIndex = 0; // If we find a prefix macro, this is what it points to int prevInterpIndex = -1; // If we find a suffix macro, this is what it points to (treating -1 as a 'null' state here) @@ -476,11 +504,13 @@ public static ProcStatus FormatString(DMProcState state) { case FormatSuffix.UpperDefiniteArticle: case FormatSuffix.LowerDefiniteArticle: { if (interps[nextInterpIndex].TryGetValueAsDreamObject(out var dreamObject)) { - bool hasName = dreamObject.TryGetVariable("name", out var objectName); - if (!hasName) continue; - string nameStr = objectName.Stringify(); - if (!DreamObject.StringIsProper(nameStr)) { - formattedString.Append(formatType == FormatSuffix.UpperDefiniteArticle ? "The " : "the "); + if (dreamObject.TryGetVariable("name", out var objectName)) { + string nameStr = objectName.Stringify(); + if (!DreamObject.StringIsProper(nameStr)) { + formattedString.Append(formatType == FormatSuffix.UpperDefiniteArticle ? "The " : "the "); + } + + objectName.Dispose(); } } @@ -501,6 +531,8 @@ public static ProcStatus FormatString(DMProcState state) { // NOTE: In Byond, this part does not work if var/gender is not a native property of this object. isPlural = (genderStr == "plural"); } + + gender.Dispose(); } else if (interpValue.TryGetValueAsString(out var interpStr)) { displayName = interpStr; } else { @@ -633,17 +665,21 @@ public static ProcStatus FormatString(DMProcState state) { } } + foreach (var interp in interps) + interp.Dispose(); + state.Push(new DreamValue(formattedString.ToString())); return ProcStatus.Continue; } public static ProcStatus Initial(DMProcState state) { - DreamValue key = state.Pop(); - DreamValue owner = state.Pop(); + using var key = state.Pop(); + using var owner = state.Pop(); // number indices always perform a normal list access here if (key.TryGetValueAsInteger(out _)) { - var indexResult = state.GetIndex(owner, key, state); + using var indexResult = state.GetIndex(owner, key, state); + state.Push(indexResult); return ProcStatus.Continue; } @@ -691,23 +727,34 @@ public static ProcStatus Initial(DMProcState state) { } public static ProcStatus IsNull(DMProcState state) { - DreamValue value = state.Pop(); + using var value = state.Pop(); state.Push(new DreamValue((value.IsNull) ? 1 : 0)); return ProcStatus.Continue; } public static ProcStatus IsInList(DMProcState state) { - DreamValue listValue = state.Pop(); - DreamValue value = state.Pop(); + using var listValue = state.Pop(); + using var value = state.Pop(); if (listValue.TryGetValueAsDreamObject(out var listObject) && listObject != null) { - var list = listObject switch { - DreamObjectAtom or DreamObjectWorld => listObject.GetVariable("contents").MustGetValueAsDreamList(), - DreamObjectSavefile savefile => new SavefileDirList(state.Proc.ObjectTree.List.ObjectDefinition, savefile), - IDreamList dreamList => dreamList, - _ => null - }; + IDreamList list; + switch (listObject) { + case DreamObjectAtom or DreamObjectWorld: + using (var contents = listObject.GetVariable("contents")) + list = contents.MustGetValueAsDreamList(); + + break; + case DreamObjectSavefile savefile: + list = new SavefileDirList(state.Proc.ObjectTree.List.ObjectDefinition, savefile); + break; + case IDreamList dreamList: + list = dreamList; + break; + default: + list = (IDreamList?)null; + break; + } if (list != null) { state.Push(new DreamValue(list.ContainsValue(value) ? 1 : 0)); @@ -773,7 +820,10 @@ public static ProcStatus PushString(DMProcState state) { } public static ProcStatus PushGlobalVars(DMProcState state) { - state.Push(new DreamValue(new DreamGlobalVars(state.Proc.ObjectTree.List.ObjectDefinition))); + var globalVarsList = new DreamGlobalVars(state.Proc.ObjectTree.List.ObjectDefinition); + + state.Push(new DreamValue(globalVarsList)); + globalVarsList.DecRef(); return ProcStatus.Continue; } @@ -782,16 +832,22 @@ public static ProcStatus PushGlobalVars(DMProcState state) { #region Math public static ProcStatus Add(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); + + if (first.TryGetValueAsDreamObject(out var firstDreamObject)) { + using var result = firstDreamObject.OperatorAdd(second, state); + + state.Push(result); + return ProcStatus.Continue; + } + DreamValue output = default; if (first.IsNull) { output = second; } else if (first.TryGetValueAsDreamResource(out _) || first.TryGetValueAsDreamObject(out _)) { output = IconOperationAdd(state, first, second); - } else if (first.TryGetValueAsDreamObject(out var firstDreamObject)) { - output = firstDreamObject.OperatorAdd(second, state); } else if (first.TryGetValueAsType(out _) || first.TryGetValueAsProc(out _)) { output = default; // Always errors } else if (second.IsNull) { @@ -811,7 +867,7 @@ public static ProcStatus Add(DMProcState state) { break; } - if (output.Type != 0) { + if (output.Type != default) { state.Push(output); } else { ThrowInvalidAddOperation(first, second); @@ -826,7 +882,9 @@ private static void ThrowInvalidAddOperation(DreamValue first, DreamValue second } public static ProcStatus Append(DMProcState state) { - state.Push(AppendHelper(state)); + using var result = AppendHelper(state); + + state.Push(result); return ProcStatus.Continue; } @@ -834,14 +892,15 @@ public static ProcStatus Append(DMProcState state) { /// Identical to except it never pushes the result to the stack /// public static ProcStatus AppendNoPush(DMProcState state) { - AppendHelper(state); + AppendHelper(state).Dispose(); return ProcStatus.Continue; } + [MustDisposeResource] private static DreamValue AppendHelper(DMProcState state) { DreamReference reference = state.ReadReference(); - DreamValue second = state.Pop(); - DreamValue first = state.GetReferenceValue(reference, peek: true); + using var second = state.Pop(); + using var first = state.GetReferenceValue(reference, peek: true); DreamValue result; if (first.TryGetValueAsDreamResource(out _) || first.TryGetValueAsDreamObject(out _)) { @@ -853,6 +912,7 @@ private static DreamValue AppendHelper(DMProcState state) { } result = second; + result.IncRef(); } else if (!second.IsNull) { switch (first.Type) { case DreamValue.DreamValueType.Float when second.Type == DreamValue.DreamValueType.Float: @@ -866,6 +926,7 @@ private static DreamValue AppendHelper(DMProcState state) { } } else { result = first; + result.IncRef(); } state.AssignReference(reference, result); @@ -873,8 +934,8 @@ private static DreamValue AppendHelper(DMProcState state) { } public static ProcStatus Increment(DMProcState state) { - DreamReference reference = state.ReadReference(); - DreamValue value = state.GetReferenceValue(reference, peek: true); + var reference = state.ReadReference(); + using var value = state.GetReferenceValue(reference, peek: true); //If it's not a number, it turns into 1 state.AssignReference(reference, new(value.UnsafeGetValueAsFloat() + 1)); @@ -884,8 +945,8 @@ public static ProcStatus Increment(DMProcState state) { } public static ProcStatus Decrement(DMProcState state) { - DreamReference reference = state.ReadReference(); - DreamValue value = state.GetReferenceValue(reference, peek: true); + var reference = state.ReadReference(); + using var value = state.GetReferenceValue(reference, peek: true); //If it's not a number, it turns into -1 state.AssignReference(reference, new(value.UnsafeGetValueAsFloat() - 1)); @@ -895,8 +956,8 @@ public static ProcStatus Decrement(DMProcState state) { } public static ProcStatus BitAnd(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); if (!first.IsDreamObject() && !first.IsNull && !second.IsNull) { state.Push(new DreamValue(first.MustGetValueAsInteger() & second.MustGetValueAsInteger())); @@ -907,10 +968,10 @@ public static ProcStatus BitAnd(DMProcState state) { int len = list.GetLength(); for (int i = 1; i <= len; i++) { - DreamValue value = list.GetValue(new DreamValue(i)); + using var value = list.GetValue(new DreamValue(i)); if (secondList.ContainsValue(value)) { - DreamValue associativeValue = list.GetValue(value); + using var associativeValue = list.GetValue(value); newList.AddValue(value); if (!associativeValue.IsNull) newList.SetValue(value, associativeValue); @@ -920,10 +981,10 @@ public static ProcStatus BitAnd(DMProcState state) { int len = list.GetLength(); for (int i = 1; i <= len; i++) { - DreamValue value = list.GetValue(new DreamValue(i)); + using var value = list.GetValue(new DreamValue(i)); if (value == second) { - DreamValue associativeValue = list.GetValue(value); + using var associativeValue = list.GetValue(value); newList.AddValue(value); if (!associativeValue.IsNull) newList.SetValue(value, associativeValue); @@ -932,6 +993,7 @@ public static ProcStatus BitAnd(DMProcState state) { } state.Push(new DreamValue(newList)); + newList.DecRef(); } else { state.Push(new DreamValue(0)); } @@ -940,7 +1002,8 @@ public static ProcStatus BitAnd(DMProcState state) { } public static ProcStatus BitNot(DMProcState state) { - var input = state.Pop(); + using var input = state.Pop(); + if (input.TryGetValueAsInteger(out var value)) { state.Push(new DreamValue((~value) & 0xFFFFFF)); } else { @@ -955,14 +1018,15 @@ public static ProcStatus BitNot(DMProcState state) { } public static ProcStatus BitOr(DMProcState state) { // x | y - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); if (first.IsNull) { state.Push(second); } else if (first.TryGetValueAsDreamObject(out var firstDreamObject)) { // Object | y if (!first.IsNull) { - var result = firstDreamObject.OperatorOr(second, state); + using var result = firstDreamObject.OperatorOr(second, state); + state.Push(result); } else { state.Push(DreamValue.Null); @@ -985,8 +1049,8 @@ public static ProcStatus BitOr(DMProcState state) { // x } public static ProcStatus BitShiftLeft(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); switch (first.Type) { case DreamValue.DreamValueType.DreamObject when first.IsNull: @@ -1007,8 +1071,9 @@ public static ProcStatus BitShiftLeft(DMProcState state) { public static ProcStatus BitShiftLeftReference(DMProcState state) { DreamReference reference = state.ReadReference(); - DreamValue second = state.Pop(); - DreamValue first = state.GetReferenceValue(reference, peek: true); + using var second = state.Pop(); + using var first = state.GetReferenceValue(reference, peek: true); + DreamValue result; switch (first.Type) { case DreamValue.DreamValueType.DreamObject when first.IsNull: @@ -1030,8 +1095,8 @@ public static ProcStatus BitShiftLeftReference(DMProcState state) { } public static ProcStatus BitShiftRight(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); switch (first.Type) { case DreamValue.DreamValueType.DreamObject when first.IsNull: @@ -1051,9 +1116,10 @@ public static ProcStatus BitShiftRight(DMProcState state) { } public static ProcStatus BitShiftRightReference(DMProcState state) { - DreamReference reference = state.ReadReference(); - DreamValue second = state.Pop(); - DreamValue first = state.GetReferenceValue(reference, peek: true); + var reference = state.ReadReference(); + using var second = state.Pop(); + using var first = state.GetReferenceValue(reference, peek: true); + DreamValue result; switch (first.Type) { case DreamValue.DreamValueType.DreamObject when first.IsNull: @@ -1075,18 +1141,19 @@ public static ProcStatus BitShiftRightReference(DMProcState state) { } public static ProcStatus BitXor(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); + using var result = BitXorValues(state.Proc.ObjectTree, first, second); - state.Push(BitXorValues(state.Proc.ObjectTree, first, second)); + state.Push(result); return ProcStatus.Continue; } public static ProcStatus BitXorReference(DMProcState state) { - DreamValue second = state.Pop(); + using var second = state.Pop(); DreamReference reference = state.ReadReference(); - DreamValue first = state.GetReferenceValue(reference, peek: true); - DreamValue result = BitXorValues(state.Proc.ObjectTree, first, second); + using var first = state.GetReferenceValue(reference, peek: true); + using var result = BitXorValues(state.Proc.ObjectTree, first, second); state.AssignReference(reference, result); state.Push(result); @@ -1094,7 +1161,7 @@ public static ProcStatus BitXorReference(DMProcState state) { } public static ProcStatus BooleanAnd(DMProcState state) { - DreamValue a = state.Pop(); + using var a = state.Pop(); int jumpPosition = state.ReadInt(); if (!a.IsTruthy()) { @@ -1106,14 +1173,14 @@ public static ProcStatus BooleanAnd(DMProcState state) { } public static ProcStatus BooleanNot(DMProcState state) { - DreamValue value = state.Pop(); + using var value = state.Pop(); state.Push(new DreamValue(value.IsTruthy() ? 0 : 1)); return ProcStatus.Continue; } public static ProcStatus BooleanOr(DMProcState state) { - DreamValue a = state.Pop(); + using var a = state.Pop(); int jumpPosition = state.ReadInt(); if (a.IsTruthy()) { @@ -1125,16 +1192,17 @@ public static ProcStatus BooleanOr(DMProcState state) { } public static ProcStatus Combine(DMProcState state) { - DreamReference reference = state.ReadReference(); - DreamValue second = state.Pop(); - DreamValue first = state.GetReferenceValue(reference, peek: true); + var reference = state.ReadReference(); + using var second = state.Pop(); + using var first = state.GetReferenceValue(reference, peek: true); DreamValue result; if (first.TryGetValueAsDreamObject(out var firstObj)) { if (firstObj != null) { - state.PopReference(reference); - state.Push(firstObj.OperatorCombine(second)); + using var opResult = firstObj.OperatorCombine(second); + state.PopReference(reference); + state.Push(opResult); return ProcStatus.Continue; } else { result = second; @@ -1159,8 +1227,8 @@ public static ProcStatus Combine(DMProcState state) { } public static ProcStatus Divide(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); switch (first.Type) { case DreamValue.DreamValueType.DreamObject when first.IsNull: @@ -1178,7 +1246,9 @@ public static ProcStatus Divide(DMProcState state) { break; case DreamValue.DreamValueType.DreamObject: var result = first.MustGetValueAsDreamObject()!.OperatorDivide(second, state); + state.Push(result); + result.Dispose(); break; default: throw new Exception($"Invalid divide operation on {first} and {second}"); @@ -1188,16 +1258,18 @@ public static ProcStatus Divide(DMProcState state) { } public static ProcStatus DivideReference(DMProcState state) { - DreamReference reference = state.ReadReference(); - DreamValue second = state.Pop(); - DreamValue first = state.GetReferenceValue(reference, peek: true); + var reference = state.ReadReference(); + using var second = state.Pop(); + using var first = state.GetReferenceValue(reference, peek: true); + if (first.TryGetValueAsFloat(out var firstFloat) || first.IsNull) { var secondFloat = second.UnsafeGetValueAsFloat(); // Non-numbers are always treated as 0 here DreamValue result = new DreamValue(firstFloat / secondFloat); state.AssignReference(reference, result); state.Push(result); } else if (first.TryGetValueAsDreamObject(out var firstDreamObject)) { - var result = firstDreamObject.OperatorDivideRef(second, state); + using var result = firstDreamObject.OperatorDivideRef(second, state); + state.AssignReference(reference, result); state.Push(result); } else { @@ -1208,16 +1280,17 @@ public static ProcStatus DivideReference(DMProcState state) { } public static ProcStatus Mask(DMProcState state) { - DreamReference reference = state.ReadReference(); - DreamValue second = state.Pop(); - DreamValue first = state.GetReferenceValue(reference, peek: true); + var reference = state.ReadReference(); + using var second = state.Pop(); + using var first = state.GetReferenceValue(reference, peek: true); DreamValue result; switch (first.Type) { case DreamValue.DreamValueType.DreamObject when !first.IsNull: { - state.PopReference(reference); - state.Push(first.MustGetValueAsDreamObject()!.OperatorMask(second)); + using var opResult = first.MustGetValueAsDreamObject()!.OperatorMask(second); + state.PopReference(reference); + state.Push(opResult); return ProcStatus.Continue; } case DreamValue.DreamValueType.DreamObject: // null @@ -1239,26 +1312,26 @@ public static ProcStatus Mask(DMProcState state) { } public static ProcStatus Modulus(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); + state.Push(ModulusValues(first, second)); return ProcStatus.Continue; } public static ProcStatus ModulusModulus(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); state.Push(ModulusModulusValues(first, second)); - return ProcStatus.Continue; } public static ProcStatus ModulusReference(DMProcState state) { - DreamValue second = state.Pop(); - DreamReference reference = state.ReadReference(); - DreamValue first = state.GetReferenceValue(reference, peek: true); - DreamValue result = ModulusValues(first, second); + using var second = state.Pop(); + var reference = state.ReadReference(); + using var first = state.GetReferenceValue(reference, peek: true); + var result = ModulusValues(first, second); state.AssignReference(reference, result); state.Push(result); @@ -1266,10 +1339,10 @@ public static ProcStatus ModulusReference(DMProcState state) { } public static ProcStatus ModulusModulusReference(DMProcState state) { - DreamValue second = state.Pop(); - DreamReference reference = state.ReadReference(); - DreamValue first = state.GetReferenceValue(reference, peek: true); - DreamValue result = ModulusModulusValues(first, second); + using var second = state.Pop(); + var reference = state.ReadReference(); + using var first = state.GetReferenceValue(reference, peek: true); + var result = ModulusModulusValues(first, second); state.AssignReference(reference, result); state.Push(result); @@ -1277,14 +1350,15 @@ public static ProcStatus ModulusModulusReference(DMProcState state) { } public static ProcStatus Multiply(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); if (first.TryGetValueAsFloat(out var firstFloat) || first.IsNull) { var secondFloat = second.UnsafeGetValueAsFloat(); // Non-numbers are always treated as 0 here state.Push(new DreamValue(firstFloat * secondFloat)); } else if (first.TryGetValueAsDreamObject(out var firstDreamObject)) { - var result = firstDreamObject.OperatorMultiply(second, state); + using var result = firstDreamObject.OperatorMultiply(second, state); + state.Push(result); } else { throw new Exception($"Invalid multiply operation on {first} and {second}"); @@ -1294,16 +1368,19 @@ public static ProcStatus Multiply(DMProcState state) { } public static ProcStatus MultiplyReference(DMProcState state) { - DreamReference reference = state.ReadReference(); - DreamValue second = state.Pop(); - DreamValue first = state.GetReferenceValue(reference, peek: true); + var reference = state.ReadReference(); + using var second = state.Pop(); + using var first = state.GetReferenceValue(reference, peek: true); + if (first.TryGetValueAsFloat(out var firstFloat) || first.IsNull) { var secondFloat = second.UnsafeGetValueAsFloat(); // Non-numbers are always treated as 0 here + DreamValue result = new DreamValue(firstFloat * secondFloat); state.AssignReference(reference, result); state.Push(result); } else if (first.TryGetValueAsDreamObject(out var firstDreamObject)) { - var result = firstDreamObject.OperatorMultiplyRef(second, state); + using var result = firstDreamObject.OperatorMultiplyRef(second, state); + state.AssignReference(reference, result); state.Push(result); } else { @@ -1314,15 +1391,16 @@ public static ProcStatus MultiplyReference(DMProcState state) { } public static ProcStatus Negate(DMProcState state) { - DreamValue first = state.Pop(); + using var first = state.Pop(); float value = first.UnsafeGetValueAsFloat(); + state.Push(new DreamValue(-value)); return ProcStatus.Continue; } public static ProcStatus Power(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); if (!first.TryGetValueAsFloat(out var floatFirst) && !first.IsNull) throw new Exception($"Invalid power operation on {first} and {second}"); @@ -1334,17 +1412,19 @@ public static ProcStatus Power(DMProcState state) { } public static ProcStatus Remove(DMProcState state) { - DreamReference reference = state.ReadReference(); - DreamValue second = state.Pop(); - DreamValue first = state.GetReferenceValue(reference, peek: true); + var reference = state.ReadReference(); + using var second = state.Pop(); + using var first = state.GetReferenceValue(reference, peek: true); DreamValue result; switch (first.Type) { - case DreamValue.DreamValueType.DreamObject when !first.IsNull: - state.PopReference(reference); - state.Push(first.MustGetValueAsDreamObject()!.OperatorRemove(second)); + case DreamValue.DreamValueType.DreamObject when !first.IsNull: { + using var opResult = first.MustGetValueAsDreamObject()!.OperatorRemove(second); + state.PopReference(reference); + state.Push(opResult); return ProcStatus.Continue; + } case DreamValue.DreamValueType.DreamObject when first.IsNull: // null is treated as 0 case DreamValue.DreamValueType.Float: if (second.Type != DreamValue.DreamValueType.Float && !second.IsNull) @@ -1363,8 +1443,8 @@ public static ProcStatus Remove(DMProcState state) { } public static ProcStatus Subtract(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); DreamValue output = default; if (first.TryGetValueAsFloat(out var firstFloat) || first.IsNull) { @@ -1375,8 +1455,9 @@ public static ProcStatus Subtract(DMProcState state) { output = firstObject.OperatorSubtract(second, state); } - if (output.Type != 0) { + if (output.Type != default) { state.Push(output); + output.Dispose(); } else { throw new Exception($"Invalid subtract operation on {first} and {second}"); } @@ -1389,32 +1470,32 @@ public static ProcStatus Subtract(DMProcState state) { #region Comparisons public static ProcStatus CompareEquals(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); state.Push(new DreamValue(IsEqual(first, second) ? 1 : 0)); return ProcStatus.Continue; } public static ProcStatus CompareEquivalent(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); state.Push(new DreamValue(IsEquivalent(first, second) ? 1 : 0)); return ProcStatus.Continue; } public static ProcStatus CompareGreaterThan(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); state.Push(new DreamValue(IsGreaterThan(first, second) ? 1 : 0)); return ProcStatus.Continue; } public static ProcStatus CompareGreaterThanOrEqual(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); DreamValue result; if (first.TryGetValueAsFloat(out float lhs) && lhs == 0.0 && second.IsNull) result = new DreamValue(1); @@ -1427,16 +1508,16 @@ public static ProcStatus CompareGreaterThanOrEqual(DMProcState state) { } public static ProcStatus CompareLessThan(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); state.Push(new DreamValue(IsLessThan(first, second) ? 1 : 0)); return ProcStatus.Continue; } public static ProcStatus CompareLessThanOrEqual(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); DreamValue result; if (first.TryGetValueAsFloat(out float lhs) && lhs == 0.0 && second.IsNull) result = new DreamValue(1); @@ -1449,25 +1530,25 @@ public static ProcStatus CompareLessThanOrEqual(DMProcState state) { } public static ProcStatus CompareNotEquals(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); state.Push(new DreamValue(IsEqual(first, second) ? 0 : 1)); return ProcStatus.Continue; } public static ProcStatus CompareNotEquivalent(DMProcState state) { - DreamValue second = state.Pop(); - DreamValue first = state.Pop(); + using var second = state.Pop(); + using var first = state.Pop(); state.Push(new DreamValue(IsEquivalent(first, second) ? 0 : 1)); return ProcStatus.Continue; } public static ProcStatus IsInRange(DMProcState state) { - DreamValue end = state.Pop(); - DreamValue start = state.Pop(); - DreamValue var = state.Pop(); + var end = state.Pop(); + var start = state.Pop(); + var var = state.Pop(); if (var.Type != DreamValue.DreamValueType.Float) var = new DreamValue(0f); if (start.Type != DreamValue.DreamValueType.Float) start = new DreamValue(0f); @@ -1475,12 +1556,15 @@ public static ProcStatus IsInRange(DMProcState state) { bool inRange = (IsEqual(start, var) || IsLessThan(start, var)) && (IsEqual(var, end) || IsLessThan(var, end)); state.Push(new DreamValue(inRange ? 1 : 0)); + end.Dispose(); + start.Dispose(); + var.Dispose(); return ProcStatus.Continue; } public static ProcStatus AsType(DMProcState state) { - DreamValue typeValue = state.Pop(); - DreamValue value = state.Pop(); + using var typeValue = state.Pop(); + using var value = state.Pop(); state.Push(TypecheckHelper(typeValue, value, true)); @@ -1488,8 +1572,8 @@ public static ProcStatus AsType(DMProcState state) { } public static ProcStatus IsType(DMProcState state) { - DreamValue typeValue = state.Pop(); - DreamValue value = state.Pop(); + using var typeValue = state.Pop(); + using var value = state.Pop(); state.Push(TypecheckHelper(typeValue, value, false)); @@ -1578,12 +1662,12 @@ public static ProcStatus Call(DMProcState state) { public static ProcStatus CallStatement(DMProcState state) { var argumentsInfo = state.ReadProcArguments(); - DreamValue source = state.Pop(); + using var source = state.Pop(); switch (source.Type) { case DreamValue.DreamValueType.DreamObject: { DreamObject? dreamObject = source.MustGetValueAsDreamObject(); - DreamValue procId = state.Pop(); + using var procId = state.Pop(); DreamProc? proc = null; switch (procId.Type) { @@ -1637,7 +1721,7 @@ public static ProcStatus Jump(DMProcState state) { public static ProcStatus JumpIfFalse(DMProcState state) { int position = state.ReadInt(); - DreamValue value = state.Pop(); + using var value = state.Pop(); if (!value.IsTruthy()) { state.Jump(position); @@ -1671,7 +1755,7 @@ public static ProcStatus JumpIfTrueReference(DMProcState state) { DreamReference reference = state.ReadReference(); int position = state.ReadInt(); - var value = state.GetReferenceValue(reference, true); + using var value = state.GetReferenceValue(reference, true); if (value.IsTruthy()) { state.PopReference(reference); @@ -1686,7 +1770,7 @@ public static ProcStatus JumpIfFalseReference(DMProcState state) { DreamReference reference = state.ReadReference(); int position = state.ReadInt(); - var value = state.GetReferenceValue(reference, true); + using var value = state.GetReferenceValue(reference, true); if (!value.IsTruthy()) { state.PopReference(reference); @@ -1699,19 +1783,22 @@ public static ProcStatus JumpIfFalseReference(DMProcState state) { public static ProcStatus DereferenceField(DMProcState state) { string name = state.ReadString(); - DreamValue owner = state.Pop(); + using var owner = state.Pop(); + using var value = state.DereferenceField(owner, name); - state.Push(state.DereferenceField(owner, name)); + state.Push(value); return ProcStatus.Continue; } public static ProcStatus Return(DMProcState state) { - state.SetReturn(state.Pop()); + using var returnValue = state.Pop(); + + state.SetReturn(returnValue); return ProcStatus.Returned; } public static ProcStatus Throw(DMProcState state) { - DreamValue value = state.Pop(); + using var value = state.Pop(); throw new DMThrowException(value); } @@ -1738,7 +1825,7 @@ public static ProcStatus EndTry(DMProcState state) { } public static ProcStatus Sin(DMProcState state) { - float x = state.Pop().UnsafeGetValueAsFloat(); + float x = state.UnsafePopAsFloat(); float result = SharedOperations.Sin(x); state.Push(new DreamValue(result)); @@ -1746,7 +1833,7 @@ public static ProcStatus Sin(DMProcState state) { } public static ProcStatus Cos(DMProcState state) { - float x = state.Pop().UnsafeGetValueAsFloat(); + float x = state.UnsafePopAsFloat(); float result = SharedOperations.Cos(x); state.Push(new DreamValue(result)); @@ -1754,7 +1841,7 @@ public static ProcStatus Cos(DMProcState state) { } public static ProcStatus Tan(DMProcState state) { - float x = state.Pop().UnsafeGetValueAsFloat(); + float x = state.UnsafePopAsFloat(); float result = SharedOperations.Tan(x); state.Push(new DreamValue(result)); @@ -1762,7 +1849,7 @@ public static ProcStatus Tan(DMProcState state) { } public static ProcStatus ArcSin(DMProcState state) { - float x = state.Pop().UnsafeGetValueAsFloat(); + float x = state.UnsafePopAsFloat(); float result = SharedOperations.ArcSin(x); state.Push(new DreamValue(result)); @@ -1770,7 +1857,7 @@ public static ProcStatus ArcSin(DMProcState state) { } public static ProcStatus ArcCos(DMProcState state) { - float x = state.Pop().UnsafeGetValueAsFloat(); + float x = state.UnsafePopAsFloat(); float result = SharedOperations.ArcCos(x); state.Push(new DreamValue(result)); @@ -1778,7 +1865,7 @@ public static ProcStatus ArcCos(DMProcState state) { } public static ProcStatus ArcTan(DMProcState state) { - float a = state.Pop().UnsafeGetValueAsFloat(); + float a = state.UnsafePopAsFloat(); float result = SharedOperations.ArcTan(a); state.Push(new DreamValue(result)); @@ -1786,8 +1873,8 @@ public static ProcStatus ArcTan(DMProcState state) { } public static ProcStatus ArcTan2(DMProcState state) { - float y = state.Pop().UnsafeGetValueAsFloat(); - float x = state.Pop().UnsafeGetValueAsFloat(); + float y = state.UnsafePopAsFloat(); + float x = state.UnsafePopAsFloat(); float result = SharedOperations.ArcTan(x, y); state.Push(new DreamValue(result)); @@ -1795,7 +1882,7 @@ public static ProcStatus ArcTan2(DMProcState state) { } public static ProcStatus Sqrt(DMProcState state) { - float a = state.Pop().UnsafeGetValueAsFloat(); + float a = state.UnsafePopAsFloat(); float result = SharedOperations.Sqrt(a); state.Push(new DreamValue(result)); @@ -1803,8 +1890,8 @@ public static ProcStatus Sqrt(DMProcState state) { } public static ProcStatus Log(DMProcState state) { - float baseValue = state.Pop().UnsafeGetValueAsFloat(); - float value = state.Pop().UnsafeGetValueAsFloat(); + float baseValue = state.UnsafePopAsFloat(); + float value = state.UnsafePopAsFloat(); float result = SharedOperations.Log(value, baseValue); state.Push(new DreamValue(result)); @@ -1812,7 +1899,7 @@ public static ProcStatus Log(DMProcState state) { } public static ProcStatus LogE(DMProcState state) { - float y = state.Pop().UnsafeGetValueAsFloat(); + float y = state.UnsafePopAsFloat(); float result = SharedOperations.Log(y); state.Push(new DreamValue(result)); @@ -1820,7 +1907,7 @@ public static ProcStatus LogE(DMProcState state) { } public static ProcStatus Abs(DMProcState state) { - float a = state.Pop().UnsafeGetValueAsFloat(); + float a = state.UnsafePopAsFloat(); float result = SharedOperations.Abs(a); state.Push(new DreamValue(result)); @@ -1829,8 +1916,8 @@ public static ProcStatus Abs(DMProcState state) { public static ProcStatus SwitchCase(DMProcState state) { int casePosition = state.ReadInt(); - DreamValue testValue = state.Pop(); - DreamValue value = state.Pop(); + using var testValue = state.Pop(); + using var value = state.Pop(); if (IsEqual(value, testValue)) { state.Jump(casePosition); @@ -1843,9 +1930,9 @@ public static ProcStatus SwitchCase(DMProcState state) { public static ProcStatus SwitchCaseRange(DMProcState state) { int casePosition = state.ReadInt(); - DreamValue rangeUpper = state.Pop(); - DreamValue rangeLower = state.Pop(); - DreamValue value = state.Pop(); + using var rangeUpper = state.Pop(); + using var rangeLower = state.Pop(); + using var value = state.Pop(); bool matchesLower = IsGreaterThan(value, rangeLower) || IsEqual(value, rangeLower); bool matchesUpper = IsLessThan(value, rangeUpper) || IsEqual(value, rangeUpper); @@ -1862,7 +1949,7 @@ public static ProcStatus SwitchCaseRange(DMProcState state) { //Jump the current thread to after the spawn's code public static ProcStatus Spawn(DMProcState state) { int jumpTo = state.ReadInt(); - state.Pop().TryGetValueAsFloat(out var delay); + float delay = state.UnsafePopAsFloat(); // TODO: It'd be nicer if we could use something such as DreamThread.Spawn here // and have state.Spawn return a ProcState instead @@ -1870,13 +1957,13 @@ public static ProcStatus Spawn(DMProcState state) { //Negative delays mean the spawned code runs immediately if (delay < 0) { - newContext.Resume(); + newContext.Resume().Dispose(); // TODO: Does the rest of the proc get scheduled? // Does the value of the delay mean anything? } else { async void Wait() { await state.ProcScheduler.CreateDelay(delay); - newContext.Resume(); + newContext.Resume().Dispose(); } Wait(); @@ -1891,9 +1978,10 @@ public static ProcStatus DebuggerBreakpoint(DMProcState state) { } public static ProcStatus ReturnReferenceValue(DMProcState state) { - DreamReference reference = state.ReadReference(); + var reference = state.ReadReference(); + using var value = state.GetReferenceValue(reference); - state.SetReturn(state.GetReferenceValue(reference)); + state.SetReturn(value); return ProcStatus.Returned; } @@ -1902,8 +1990,8 @@ public static ProcStatus ReturnReferenceValue(DMProcState state) { #region Builtins public static ProcStatus GetStep(DMProcState state) { - var d = state.Pop(); - var l = state.Pop(); + using var d = state.Pop(); + using var l = state.Pop(); if (!l.TryGetValueAsDreamObject(out var loc)) { state.Push(DreamValue.Null); @@ -1917,15 +2005,15 @@ public static ProcStatus GetStep(DMProcState state) { } public static ProcStatus Length(DMProcState state) { - var o = state.Pop(); + using var o = state.Pop(); state.Push(DreamProcNativeRoot._length(o, true)); return ProcStatus.Continue; } public static ProcStatus GetDir(DMProcState state) { - var loc2R = state.Pop(); - var loc1R = state.Pop(); + using var loc2R = state.Pop(); + using var loc1R = state.Pop(); if (!loc1R.TryGetValueAsDreamObject(out var loc1)) { state.Push(new DreamValue(0)); @@ -1955,8 +2043,8 @@ public static ProcStatus Gradient(DMProcState state) { gradientValues.EnsureCapacity(argumentCount - 1); for (int i = 0; i < argumentCount; i++) { - var argumentKey = stack[i * 2]; - var argumentValue = stack[i * 2 + 1]; + using var argumentKey = stack[i * 2]; + using var argumentValue = stack[i * 2 + 1]; if (argumentKey.TryGetValueAsString(out var argumentKeyStr)) { if (argumentKeyStr == "index") { @@ -1978,7 +2066,8 @@ public static ProcStatus Gradient(DMProcState state) { gradientValues.Add(argumentValue); } } else if (argumentInfo.Type == DMCallArgumentsType.FromArgumentList) { - if (!state.Pop().TryGetValueAsDreamList(out var argList)) + using var argListStack = state.Pop(); + if (!argListStack.TryGetValueAsDreamList(out var argList)) throw new Exception("Invalid gradient() arguments"); var argListValues = argList.GetValues(); @@ -2007,7 +2096,7 @@ public static ProcStatus Gradient(DMProcState state) { gradientValues.Add(value); } } else { - var arguments = state.PopProcArguments(null, argumentInfo.Type, argumentInfo.StackSize); + using var arguments = state.PopProcArguments(null, argumentInfo.Type, argumentInfo.StackSize); gradientIndex = arguments.Values[^1]; for (int i = 0; i < arguments.Count - 1; i++) { @@ -2019,6 +2108,8 @@ public static ProcStatus Gradient(DMProcState state) { throw new Exception("No gradient index given"); state.Push(CalculateGradient(gradientValues, gradientColorSpace, gradientIndex)); + gradientColorSpace.Dispose(); + gradientIndex.Dispose(); return ProcStatus.Continue; } @@ -2027,372 +2118,419 @@ public static ProcStatus Rgb(DMProcState state) { var argumentValues = state.PopCount(argumentInfo.StackSize); var arguments = state.CollectProcArguments(argumentValues, argumentInfo.Type, argumentInfo.StackSize); - string result = "#000000"; - if (arguments.Item1 is not null) { - if (arguments.Item1.Length is < 3 or > 5) - throw new Exception("Expected 3 to 5 arguments for rgb()"); - (string?, float?)[] values = new (string?, float?)[arguments.Item1.Length]; - for (int i = 0; i < arguments.Item1.Length; i++) { - var val = arguments.Item1[i].UnsafeGetValueAsFloat(); - values[i] = (null, val); - } - - result = SharedOperations.ParseRgb(values); - } else if (arguments.Item2 != null) { - if (arguments.Item2.Count is < 3 or > 5) - throw new Exception("Expected 3 to 5 arguments for rgb()"); - (string?, float?)[] values = new (string?, float?)[5]; - DreamValue color1 = default; - DreamValue color2 = default; - DreamValue color3 = default; - DreamValue a = DreamValue.Null; - SharedOperations.ColorSpace space = SharedOperations.ColorSpace.RGB; - foreach (var arg in arguments.Item2) { - if (arg.Key.TryGetValueAsInteger(out var position)) { - switch (position) { - case 1: color1 = arg.Value; continue; - case 2: color2 = arg.Value; continue; - case 3: color3 = arg.Value; continue; - case 4: a = arg.Value; continue; - case 5: space = (SharedOperations.ColorSpace)(int)arg.Value.UnsafeGetValueAsFloat(); continue; - default: throw new Exception($"Invalid argument key {position}"); + try { + string result; + if (arguments.Item1 is not null) { + if (arguments.Item1.Length is < 3 or > 5) + throw new Exception("Expected 3 to 5 arguments for rgb()"); + (string?, float?)[] values = new (string?, float?)[arguments.Item1.Length]; + for (int i = 0; i < arguments.Item1.Length; i++) { + var val = arguments.Item1[i].UnsafeGetValueAsFloat(); + values[i] = (null, val); + } + + result = SharedOperations.ParseRgb(values); + } else if (arguments.Item2 != null) { + if (arguments.Item2.Count is < 3 or > 5) + throw new Exception("Expected 3 to 5 arguments for rgb()"); + (string?, float?)[] values = new (string?, float?)[5]; + DreamValue color1 = default; + DreamValue color2 = default; + DreamValue color3 = default; + DreamValue a = DreamValue.Null; + SharedOperations.ColorSpace space = SharedOperations.ColorSpace.RGB; + foreach (var arg in arguments.Item2) { + if (arg.Key.TryGetValueAsInteger(out var position)) { + switch (position) { + case 1: + color1 = arg.Value; + continue; + case 2: + color2 = arg.Value; + continue; + case 3: + color3 = arg.Value; + continue; + case 4: + a = arg.Value; + continue; + case 5: + space = (SharedOperations.ColorSpace)(int)arg.Value.UnsafeGetValueAsFloat(); + continue; + default: throw new Exception($"Invalid argument key {position}"); + } + } else { + var name = arg.Key.MustGetValueAsString(); + + if (name.StartsWith("r", StringComparison.InvariantCultureIgnoreCase) && + color1 == default) { + color1 = arg.Value; + space = SharedOperations.ColorSpace.RGB; + } else if (name.StartsWith("g", StringComparison.InvariantCultureIgnoreCase) && + color2 == default) { + color2 = arg.Value; + space = SharedOperations.ColorSpace.RGB; + } else if (name.StartsWith("b", StringComparison.InvariantCultureIgnoreCase) && + color3 == default) { + color3 = arg.Value; + space = SharedOperations.ColorSpace.RGB; + } else if (name.StartsWith("h", StringComparison.InvariantCultureIgnoreCase) && + color1 == default) { + color1 = arg.Value; + space = SharedOperations.ColorSpace.HSV; + } else if (name != "space" && + name.StartsWith("s", StringComparison.InvariantCultureIgnoreCase) && + color2 == default) { + color2 = arg.Value; + space = SharedOperations.ColorSpace.HSV; + } else if (name.StartsWith("v", StringComparison.InvariantCultureIgnoreCase) && + color3 == default) { + color3 = arg.Value; + space = SharedOperations.ColorSpace.HSV; + } else if (name.StartsWith("l", StringComparison.InvariantCultureIgnoreCase) && + color3 == default) { + color3 = arg.Value; + space = SharedOperations.ColorSpace.HSL; + } else if (name.StartsWith("a", StringComparison.InvariantCultureIgnoreCase) && + a == default) + a = arg.Value; + else if (name == "space" && space == default) + space = (SharedOperations.ColorSpace)(int)arg.Value.UnsafeGetValueAsFloat(); + else + throw new Exception($"Invalid or double arg \"{name}\""); } - } else { - var name = arg.Key.MustGetValueAsString(); - - if (name.StartsWith("r", StringComparison.InvariantCultureIgnoreCase) && color1 == default) { - color1 = arg.Value; - space = SharedOperations.ColorSpace.RGB; - } else if (name.StartsWith("g", StringComparison.InvariantCultureIgnoreCase) && color2 == default) { - color2 = arg.Value; - space = SharedOperations.ColorSpace.RGB; - } else if (name.StartsWith("b", StringComparison.InvariantCultureIgnoreCase) && color3 == default) { - color3 = arg.Value; - space = SharedOperations.ColorSpace.RGB; - } else if (name.StartsWith("h", StringComparison.InvariantCultureIgnoreCase) && color1 == default) { - color1 = arg.Value; - space = SharedOperations.ColorSpace.HSV; - } else if (name != "space" && name.StartsWith("s", StringComparison.InvariantCultureIgnoreCase) && color2 == default) { - color2 = arg.Value; - space = SharedOperations.ColorSpace.HSV; - } else if (name.StartsWith("v", StringComparison.InvariantCultureIgnoreCase) && color3 == default) { - color3 = arg.Value; - space = SharedOperations.ColorSpace.HSV; - } else if (name.StartsWith("l", StringComparison.InvariantCultureIgnoreCase) && color3 == default) { - color3 = arg.Value; - space = SharedOperations.ColorSpace.HSL; - } else if (name.StartsWith("a", StringComparison.InvariantCultureIgnoreCase) && a == default) - a = arg.Value; - else if (name == "space" && space == default) - space = (SharedOperations.ColorSpace)(int)arg.Value.UnsafeGetValueAsFloat(); - else - throw new Exception($"Invalid or double arg \"{name}\""); } - } - values[0] = (null, color1.UnsafeGetValueAsFloat()); - values[1] = (null, color2.UnsafeGetValueAsFloat()); - values[2] = (null, color3.UnsafeGetValueAsFloat()); - if(a.TryGetValueAsFloat(out var aVal)) - values[3] = (null, aVal); - else - values[3] = (null, null); - values[4] = (null, (float)space); + values[0] = (null, color1.UnsafeGetValueAsFloat()); + values[1] = (null, color2.UnsafeGetValueAsFloat()); + values[2] = (null, color3.UnsafeGetValueAsFloat()); + if (a.TryGetValueAsFloat(out var aVal)) + values[3] = (null, aVal); + else + values[3] = (null, null); + values[4] = (null, (float)space); - result = SharedOperations.ParseRgb(values); - } else { - result = "#000000"; - } + result = SharedOperations.ParseRgb(values); + } else { + result = "#000000"; + } - state.Push(new DreamValue(result)); - return ProcStatus.Continue; + state.Push(new DreamValue(result)); + return ProcStatus.Continue; + } finally { + foreach (var argument in argumentValues) + argument.Dispose(); + } } -/* vars: + /* vars: -animate smoothly: + animate smoothly: -alpha -color -glide_size -infra_luminosity -layer -maptext_width, maptext_height, maptext_x, maptext_y -luminosity -pixel_x, pixel_y, pixel_w, pixel_z -transform + alpha + color + glide_size + infra_luminosity + layer + maptext_width, maptext_height, maptext_x, maptext_y + luminosity + pixel_x, pixel_y, pixel_w, pixel_z + transform -do not animate smoothly: + do not animate smoothly: -dir -icon -icon_state -invisibility -maptext -suffix + dir + icon + icon_state + invisibility + maptext + suffix -*/ + */ public static ProcStatus Animate(DMProcState state) { var argumentInfo = state.ReadProcArguments(); var argumentValues = state.PopCount(argumentInfo.StackSize); var arguments = state.CollectProcArguments(argumentValues, argumentInfo.Type, argumentInfo.StackSize); - bool chainAnim = false; + try { + bool chainAnim = false; + + if (!GetArgument(arguments.Item1, arguments.Item2, 1, "Object", DreamValue.Null) + .TryGetValueAsDreamObject(out var obj)) { + if (state.Thread.LastAnimatedObject is null || state.Thread.LastAnimatedObject.Value.IsNull) + throw new Exception("animate() called without an object and no previous object to animate"); + else if (!state.Thread.LastAnimatedObject.Value.TryGetValueAsDreamObject(out obj)) { + state.Push(DreamValue.Null); + return ProcStatus.Continue; + } + + chainAnim = true; + } - if (!GetArgument(arguments.Item1, arguments.Item2, 1, "Object", DreamValue.Null).TryGetValueAsDreamObject(out var obj)) { - if (state.Thread.LastAnimatedObject is null || state.Thread.LastAnimatedObject.Value.IsNull) - throw new Exception("animate() called without an object and no previous object to animate"); - else if (!state.Thread.LastAnimatedObject.Value.TryGetValueAsDreamObject(out obj)){ + state.Thread.LastAnimatedObject = new DreamValue(obj); + if (obj.IsSubtypeOf(state.Proc.ObjectTree.Filter)) { //TODO animate filters state.Push(DreamValue.Null); return ProcStatus.Continue; } - chainAnim = true; - } - - state.Thread.LastAnimatedObject = new DreamValue(obj); - if (obj.IsSubtypeOf(state.Proc.ObjectTree.Filter)) {//TODO animate filters - state.Push(DreamValue.Null); - return ProcStatus.Continue; - } - - // TODO: Is this the correct behavior for invalid time? - if (!GetArgument(arguments.Item1, arguments.Item2, 2, "time", DreamValue.Null).TryGetValueAsFloat(out float time)) { - state.Push(DreamValue.Null); - return ProcStatus.Continue; - } - - GetArgument(arguments.Item1, arguments.Item2, 3, "loop", DreamValue.Null).TryGetValueAsInteger(out int loop); - GetArgument(arguments.Item1, arguments.Item2, 4, "easing", DreamValue.Null).TryGetValueAsInteger(out int easing); - if (!Enum.IsDefined(typeof(AnimationEasing), easing & ~((int)AnimationEasing.EaseIn | (int)AnimationEasing.EaseOut))) - throw new ArgumentOutOfRangeException("easing", easing, $"Invalid easing value in animate(): {easing}"); - GetArgument(arguments.Item1, arguments.Item2, 5, "flags", DreamValue.Null).TryGetValueAsInteger(out int flagsInt); - var flags = (AnimationFlags)flagsInt; - if ((flags & (AnimationFlags.AnimationParallel | AnimationFlags.AnimationContinue)) != 0) - chainAnim = true; - if ((flags & AnimationFlags.AnimationEndNow) != 0) - chainAnim = false; - GetArgument(arguments.Item1, arguments.Item2, 6, "delay", DreamValue.Null).TryGetValueAsInteger(out int delay); - - var pixelX = GetArgument(arguments.Item1, arguments.Item2, 7, "pixel_x", DreamValue.Null); - var pixelY = GetArgument(arguments.Item1, arguments.Item2, 8, "pixel_y", DreamValue.Null); - var pixelZ = GetArgument(arguments.Item1, arguments.Item2, 9, "pixel_z", DreamValue.Null); - var pixelW = GetArgument(arguments.Item1, arguments.Item2, 10, "pixel_w", DreamValue.Null); - var maptext = GetArgument(arguments.Item1, arguments.Item2, 11, "maptext", DreamValue.Null); - var maptextWidth = GetArgument(arguments.Item1, arguments.Item2, 12, "maptext_width", DreamValue.Null); - var maptextHeight = GetArgument(arguments.Item1, arguments.Item2, 13, "maptext_height", DreamValue.Null); - var maptextX = GetArgument(arguments.Item1, arguments.Item2, 14, "maptext_x", DreamValue.Null); - var maptextY = GetArgument(arguments.Item1, arguments.Item2, 15, "maptext_y", DreamValue.Null); - var dir = GetArgument(arguments.Item1, arguments.Item2, 16, "dir", DreamValue.Null); - var alpha = GetArgument(arguments.Item1, arguments.Item2, 17, "alpha", DreamValue.Null); - var isTransformDefined = IsArgumentDefined(arguments.Item1, arguments.Item2, 18, "transform", DreamValue.Null, out var transform); - if (isTransformDefined && transform.IsNull) { - // when transform is null because it was provided as null, treat as identity matrix - DreamObjectMatrix identityTransform = DreamObjectMatrix.MakeMatrix(state.Proc.ObjectTree, 1f, 0f, 0f, 0f, 1f, 0f); - transform = new(identityTransform); - } - - var color = GetArgument(arguments.Item1, arguments.Item2, 19, "color", DreamValue.Null); - var luminosity = GetArgument(arguments.Item1, arguments.Item2, 20, "luminosity", DreamValue.Null); - var infraLuminosity = GetArgument(arguments.Item1, arguments.Item2, 21, "infra_luminosity", DreamValue.Null); - var layer = GetArgument(arguments.Item1, arguments.Item2, 22, "layer", DreamValue.Null); - var glideSize = GetArgument(arguments.Item1, arguments.Item2, 23, "glide_size", DreamValue.Null); - var icon = GetArgument(arguments.Item1, arguments.Item2, 24, "icon", DreamValue.Null); - var iconState = GetArgument(arguments.Item1, arguments.Item2, 25, "icon_state", DreamValue.Null); - var invisibility = GetArgument(arguments.Item1, arguments.Item2, 26, "invisibility", DreamValue.Null); - var suffix = GetArgument(arguments.Item1, arguments.Item2, 27, "suffix", DreamValue.Null); - - if ((flags & AnimationFlags.AnimationRelative) != 0) { - if (!state.Proc.AtomManager.TryGetAppearance(obj, out var appearance)) { - //can't do anything animating an object with no appearance - // This works for maptext_x/y/width/height, pixel_x/y/w/z, luminosity, layer, alpha, transform, and color. For transform and color, the current value is multiplied by the new one. Vars not in this list are simply changed as if this flag is not present. + // TODO: Is this the correct behavior for invalid time? + if (!GetArgument(arguments.Item1, arguments.Item2, 2, "time", DreamValue.Null) + .TryGetValueAsFloat(out float time)) { state.Push(DreamValue.Null); return ProcStatus.Continue; } - if (!pixelX.IsNull) - pixelX = new(pixelX.UnsafeGetValueAsFloat() + appearance.PixelOffset.X); - if (!pixelY.IsNull) - pixelY = new(pixelY.UnsafeGetValueAsFloat() + appearance.PixelOffset.Y); - /* TODO these are not yet implemented - if(!pixelZ.IsNull) - pixelZ = new(pixelZ.UnsafeGetValueAsFloat() + obj.GetVariable("pixel_z").UnsafeGetValueAsFloat()); //TODO change to appearance when pixel_z is implemented - */ - if (!maptextWidth.IsNull) - maptextWidth = new(maptextWidth.UnsafeGetValueAsFloat() + appearance.MaptextSize.X); - if (!maptextHeight.IsNull) - maptextHeight = new(maptextHeight.UnsafeGetValueAsFloat() + appearance.MaptextSize.Y); - if (!maptextX.IsNull) - maptextX = new(maptextX.UnsafeGetValueAsFloat() + appearance.MaptextOffset.X); - if (!maptextY.IsNull) - maptextY = new(maptextY.UnsafeGetValueAsFloat() + appearance.MaptextOffset.Y); - /* - if(!luminosity.IsNull) - luminosity = new(luminosity.UnsafeGetValueAsFloat() + obj.GetVariable("luminosity").UnsafeGetValueAsFloat()); //TODO change to appearance when luminosity is implemented - */ - if (!layer.IsNull) - layer = new(layer.UnsafeGetValueAsFloat() + appearance.Layer); - if (!alpha.IsNull) - alpha = new(alpha.UnsafeGetValueAsFloat() + appearance.Alpha); - if (!transform.IsNull) { - if (transform.TryGetValueAsDreamObject(out var multTransform)) { - DreamObjectMatrix objTransformClone = DreamObjectMatrix.MakeMatrix(state.Proc.ObjectTree, appearance.Transform); - DreamObjectMatrix.MultiplyMatrix(objTransformClone, multTransform); - transform = new(objTransformClone); + GetArgument(arguments.Item1, arguments.Item2, 3, "loop", DreamValue.Null) + .TryGetValueAsInteger(out int loop); + GetArgument(arguments.Item1, arguments.Item2, 4, "easing", DreamValue.Null) + .TryGetValueAsInteger(out int easing); + if (!Enum.IsDefined(typeof(AnimationEasing), + easing & ~((int)AnimationEasing.EaseIn | (int)AnimationEasing.EaseOut))) + throw new ArgumentOutOfRangeException("easing", easing, + $"Invalid easing value in animate(): {easing}"); + GetArgument(arguments.Item1, arguments.Item2, 5, "flags", DreamValue.Null) + .TryGetValueAsInteger(out int flagsInt); + var flags = (AnimationFlags)flagsInt; + if ((flags & (AnimationFlags.AnimationParallel | AnimationFlags.AnimationContinue)) != 0) + chainAnim = true; + if ((flags & AnimationFlags.AnimationEndNow) != 0) + chainAnim = false; + GetArgument(arguments.Item1, arguments.Item2, 6, "delay", DreamValue.Null) + .TryGetValueAsInteger(out int delay); + + var pixelX = GetArgument(arguments.Item1, arguments.Item2, 7, "pixel_x", DreamValue.Null); + var pixelY = GetArgument(arguments.Item1, arguments.Item2, 8, "pixel_y", DreamValue.Null); + var pixelZ = GetArgument(arguments.Item1, arguments.Item2, 9, "pixel_z", DreamValue.Null); + var pixelW = GetArgument(arguments.Item1, arguments.Item2, 10, "pixel_w", DreamValue.Null); + var maptext = GetArgument(arguments.Item1, arguments.Item2, 11, "maptext", DreamValue.Null); + var maptextWidth = GetArgument(arguments.Item1, arguments.Item2, 12, "maptext_width", DreamValue.Null); + var maptextHeight = + GetArgument(arguments.Item1, arguments.Item2, 13, "maptext_height", DreamValue.Null); + var maptextX = GetArgument(arguments.Item1, arguments.Item2, 14, "maptext_x", DreamValue.Null); + var maptextY = GetArgument(arguments.Item1, arguments.Item2, 15, "maptext_y", DreamValue.Null); + var dir = GetArgument(arguments.Item1, arguments.Item2, 16, "dir", DreamValue.Null); + var alpha = GetArgument(arguments.Item1, arguments.Item2, 17, "alpha", DreamValue.Null); + var isTransformDefined = IsArgumentDefined(arguments.Item1, arguments.Item2, 18, "transform", + DreamValue.Null, out var transform); + if (isTransformDefined && transform.IsNull) { + // when transform is null because it was provided as null, treat as identity matrix + DreamObjectMatrix identityTransform = + DreamObjectMatrix.MakeMatrix(state.Proc.ObjectTree, 1f, 0f, 0f, 0f, 1f, 0f); + transform = new(identityTransform); + } + + var color = GetArgument(arguments.Item1, arguments.Item2, 19, "color", DreamValue.Null); + var luminosity = GetArgument(arguments.Item1, arguments.Item2, 20, "luminosity", DreamValue.Null); + var infraLuminosity = GetArgument(arguments.Item1, arguments.Item2, 21, "infra_luminosity", + DreamValue.Null); + var layer = GetArgument(arguments.Item1, arguments.Item2, 22, "layer", DreamValue.Null); + var glideSize = GetArgument(arguments.Item1, arguments.Item2, 23, "glide_size", DreamValue.Null); + var icon = GetArgument(arguments.Item1, arguments.Item2, 24, "icon", DreamValue.Null); + var iconState = GetArgument(arguments.Item1, arguments.Item2, 25, "icon_state", DreamValue.Null); + var invisibility = GetArgument(arguments.Item1, arguments.Item2, 26, "invisibility", DreamValue.Null); + var suffix = GetArgument(arguments.Item1, arguments.Item2, 27, "suffix", DreamValue.Null); + + if ((flags & AnimationFlags.AnimationRelative) != 0) { + if (!state.Proc.AtomManager.TryGetAppearance(obj, out var appearance)) { + //can't do anything animating an object with no appearance + // This works for maptext_x/y/width/height, pixel_x/y/w/z, luminosity, layer, alpha, transform, and color. For transform and color, the current value is multiplied by the new one. Vars not in this list are simply changed as if this flag is not present. + state.Push(DreamValue.Null); + return ProcStatus.Continue; } - } - if (!color.IsNull) { - ColorMatrix cMatrix; - if (color.TryGetValueAsString(out var colorStr) && Color.TryParse(colorStr, out var colorObj)) { - cMatrix = new ColorMatrix(colorObj); - } else if (!color.TryGetValueAsDreamList(out var colorList) || !DreamProcNativeHelpers.TryParseColorMatrix(colorList, out cMatrix)) { - cMatrix = ColorMatrix.Identity; //fallback to identity if invalid + if (!pixelX.IsNull) + pixelX = new(pixelX.UnsafeGetValueAsFloat() + appearance.PixelOffset.X); + if (!pixelY.IsNull) + pixelY = new(pixelY.UnsafeGetValueAsFloat() + appearance.PixelOffset.Y); + /* TODO these are not yet implemented + if(!pixelZ.IsNull) + pixelZ = new(pixelZ.UnsafeGetValueAsFloat() + obj.GetVariable("pixel_z").UnsafeGetValueAsFloat()); //TODO change to appearance when pixel_z is implemented + */ + if (!maptextWidth.IsNull) + maptextWidth = new(maptextWidth.UnsafeGetValueAsFloat() + appearance.MaptextSize.X); + if (!maptextHeight.IsNull) + maptextHeight = new(maptextHeight.UnsafeGetValueAsFloat() + appearance.MaptextSize.Y); + if (!maptextX.IsNull) + maptextX = new(maptextX.UnsafeGetValueAsFloat() + appearance.MaptextOffset.X); + if (!maptextY.IsNull) + maptextY = new(maptextY.UnsafeGetValueAsFloat() + appearance.MaptextOffset.Y); + /* + if(!luminosity.IsNull) + luminosity = new(luminosity.UnsafeGetValueAsFloat() + obj.GetVariable("luminosity").UnsafeGetValueAsFloat()); //TODO change to appearance when luminosity is implemented + */ + if (!layer.IsNull) + layer = new(layer.UnsafeGetValueAsFloat() + appearance.Layer); + if (!alpha.IsNull) + alpha = new(alpha.UnsafeGetValueAsFloat() + appearance.Alpha); + if (!transform.IsNull) { + if (transform.TryGetValueAsDreamObject(out var multTransform)) { + DreamObjectMatrix objTransformClone = + DreamObjectMatrix.MakeMatrix(state.Proc.ObjectTree, appearance.Transform); + DreamObjectMatrix.MultiplyMatrix(objTransformClone, multTransform); + transform = new(objTransformClone); + } } - ColorMatrix objCMatrix; - DreamValue objColor = obj.GetVariable("color"); - if (objColor.TryGetValueAsString(out var objColorStr) && Color.TryParse(objColorStr, out var objColorObj)) { - objCMatrix = new ColorMatrix(objColorObj); - } else if (!objColor.TryGetValueAsDreamList(out var objColorList) || !DreamProcNativeHelpers.TryParseColorMatrix(objColorList, out objCMatrix)) { - objCMatrix = ColorMatrix.Identity; //fallback to identity if invalid - } + if (!color.IsNull) { + ColorMatrix cMatrix; + if (color.TryGetValueAsString(out var colorStr) && Color.TryParse(colorStr, out var colorObj)) { + cMatrix = new ColorMatrix(colorObj); + } else if (!color.TryGetValueAsDreamList(out var colorList) || + !DreamProcNativeHelpers.TryParseColorMatrix(colorList, out cMatrix)) { + cMatrix = ColorMatrix.Identity; //fallback to identity if invalid + } - ColorMatrix.Multiply(ref objCMatrix, ref cMatrix, out var resultMatrix); - color = new DreamValue(new DreamList(state.Proc.ObjectTree.List.ObjectDefinition, resultMatrix.GetValues().Select(x => new DreamValue(x)).ToList(), null)); - } - } + ColorMatrix objCMatrix; + using var objColor = obj.GetVariable("color"); + if (objColor.TryGetValueAsString(out var objColorStr) && + Color.TryParse(objColorStr, out var objColorObj)) { + objCMatrix = new ColorMatrix(objColorObj); + } else if (!objColor.TryGetValueAsDreamList(out var objColorList) || + !DreamProcNativeHelpers.TryParseColorMatrix(objColorList, out objCMatrix)) { + objCMatrix = ColorMatrix.Identity; //fallback to identity if invalid + } - var resourceManager = state.Proc.DreamResourceManager; - state.Proc.AtomManager.AnimateAppearance(obj, TimeSpan.FromMilliseconds(time * 100), (AnimationEasing)easing, loop, flags, delay, chainAnim, - appearance => { - if (!pixelX.IsNull) { - obj.SetVariableValue("pixel_x", pixelX); - pixelX.TryGetValueAsInteger(out appearance.PixelOffset.X); + ColorMatrix.Multiply(ref objCMatrix, ref cMatrix, out var resultMatrix); + color = new DreamValue(new DreamList(state.Proc.ObjectTree.List.ObjectDefinition, + resultMatrix.GetValues().Select(x => new DreamValue(x)).ToList(), null)); + } } - if (!pixelY.IsNull) { - obj.SetVariableValue("pixel_y", pixelY); - pixelY.TryGetValueAsInteger(out appearance.PixelOffset.Y); - } + var resourceManager = state.Proc.DreamResourceManager; + state.Proc.AtomManager.AnimateAppearance(obj, TimeSpan.FromMilliseconds(time * 100), + (AnimationEasing)easing, loop, flags, delay, chainAnim, + appearance => { + if (!pixelX.IsNull) { + obj.SetVariableValue("pixel_x", pixelX); + pixelX.TryGetValueAsInteger(out appearance.PixelOffset.X); + } - /* TODO world.map_format - if (!pixelZ.IsNull) { - obj.SetVariableValue("pixel_z", pixelZ); - pixelZ.TryGetValueAsInteger(out appearance.PixelOffset.Z); - } - */ + if (!pixelY.IsNull) { + obj.SetVariableValue("pixel_y", pixelY); + pixelY.TryGetValueAsInteger(out appearance.PixelOffset.Y); + } - if (!maptextX.IsNull) { - obj.SetVariableValue("maptext_x", maptextX); - maptextX.TryGetValueAsInteger(out appearance.MaptextOffset.X); - } + /* TODO world.map_format + if (!pixelZ.IsNull) { + obj.SetVariableValue("pixel_z", pixelZ); + pixelZ.TryGetValueAsInteger(out appearance.PixelOffset.Z); + } + */ - if (!maptextY.IsNull) { - obj.SetVariableValue("maptext_y", maptextY); - maptextY.TryGetValueAsInteger(out appearance.MaptextOffset.Y); - } + if (!maptextX.IsNull) { + obj.SetVariableValue("maptext_x", maptextX); + maptextX.TryGetValueAsInteger(out appearance.MaptextOffset.X); + } - if (!maptextWidth.IsNull) { - obj.SetVariableValue("maptext_width", maptextWidth); - maptextX.TryGetValueAsInteger(out appearance.MaptextSize.X); - } + if (!maptextY.IsNull) { + obj.SetVariableValue("maptext_y", maptextY); + maptextY.TryGetValueAsInteger(out appearance.MaptextOffset.Y); + } - if (!maptextHeight.IsNull) { - obj.SetVariableValue("maptext_y", maptextHeight); - maptextY.TryGetValueAsInteger(out appearance.MaptextSize.Y); - } + if (!maptextWidth.IsNull) { + obj.SetVariableValue("maptext_width", maptextWidth); + maptextX.TryGetValueAsInteger(out appearance.MaptextSize.X); + } - if (!maptext.IsNull) { - obj.SetVariableValue("maptext", maptext); - maptext.TryGetValueAsString(out appearance.Maptext); - } + if (!maptextHeight.IsNull) { + obj.SetVariableValue("maptext_y", maptextHeight); + maptextY.TryGetValueAsInteger(out appearance.MaptextSize.Y); + } - if (!dir.IsNull) { - obj.SetVariableValue("dir", dir); - if (dir.TryGetValueAsInteger(out int dirValue)) - appearance.Direction = (AtomDirection)dirValue; - } + if (!maptext.IsNull) { + obj.SetVariableValue("maptext", maptext); + maptext.TryGetValueAsString(out appearance.Maptext); + } - if (!alpha.IsNull) { - obj.SetVariableValue("alpha", alpha); - if (alpha.TryGetValueAsInteger(out var alphaInt)) - appearance.Alpha = (byte)Math.Clamp(alphaInt, 0, 255); - } + if (!dir.IsNull) { + obj.SetVariableValue("dir", dir); + if (dir.TryGetValueAsInteger(out int dirValue)) + appearance.Direction = (AtomDirection)dirValue; + } - if (!transform.IsNull) { - obj.SetVariableValue("transform", transform); - if (transform.TryGetValueAsDreamObject(out var transformObj)) - appearance.Transform = DreamObjectMatrix.MatrixToTransformFloatArray(transformObj); - } + if (!alpha.IsNull) { + obj.SetVariableValue("alpha", alpha); + if (alpha.TryGetValueAsInteger(out var alphaInt)) + appearance.Alpha = (byte)Math.Clamp(alphaInt, 0, 255); + } - if (!color.IsNull) { - obj.SetVariableValue("color", color); - if (color.TryGetValueAsString(out var colorStr)) - Color.TryParse(colorStr, out appearance.Color); - else if (color.TryGetValueAsDreamList(out var colorList)) { - if (DreamProcNativeHelpers.TryParseColorMatrix(colorList, out var colorMatrix)) - appearance.ColorMatrix = colorMatrix; - } - } + if (!transform.IsNull) { + obj.SetVariableValue("transform", transform); + if (transform.TryGetValueAsDreamObject(out var transformObj)) + appearance.Transform = DreamObjectMatrix.MatrixToTransformFloatArray(transformObj); + } - /* TODO luminosity - if (!luminosity.IsNull) { - obj.SetVariableValue("luminosity", luminosity); - luminosity.TryGetValueAsInteger(out appearance.Luminosity); - } - */ + if (!color.IsNull) { + obj.SetVariableValue("color", color); + if (color.TryGetValueAsString(out var colorStr)) + Color.TryParse(colorStr, out appearance.Color); + else if (color.TryGetValueAsDreamList(out var colorList)) { + if (DreamProcNativeHelpers.TryParseColorMatrix(colorList, out var colorMatrix)) + appearance.ColorMatrix = colorMatrix; + } + } - /* TODO infra_luminosity - if (!infraLuminosity.IsNull) { - obj.SetVariableValue("infra_luminosity", infraLuminosity); - infraLuminosity.TryGetValueAsInteger(out appearance.InfraLuminosity); - } - */ + /* TODO luminosity + if (!luminosity.IsNull) { + obj.SetVariableValue("luminosity", luminosity); + luminosity.TryGetValueAsInteger(out appearance.Luminosity); + } + */ - if (!layer.IsNull) { - obj.SetVariableValue("layer", layer); - layer.TryGetValueAsFloat(out appearance.Layer); - } + /* TODO infra_luminosity + if (!infraLuminosity.IsNull) { + obj.SetVariableValue("infra_luminosity", infraLuminosity); + infraLuminosity.TryGetValueAsInteger(out appearance.InfraLuminosity); + } + */ - if (!glideSize.IsNull) { - obj.SetVariableValue("glide_size", glideSize); - glideSize.TryGetValueAsFloat(out appearance.GlideSize); - } + if (!layer.IsNull) { + obj.SetVariableValue("layer", layer); + layer.TryGetValueAsFloat(out appearance.Layer); + } - if (!icon.IsNull) { - obj.SetVariableValue("icon", icon); - if (resourceManager.TryLoadIcon(icon, out var iconResource)) - appearance.Icon = iconResource.Id; - } + if (!glideSize.IsNull) { + obj.SetVariableValue("glide_size", glideSize); + glideSize.TryGetValueAsFloat(out appearance.GlideSize); + } - if (!iconState.IsNull) { - obj.SetVariableValue("icon_state", iconState); - iconState.TryGetValueAsString(out appearance.IconState); - } + if (!icon.IsNull) { + obj.SetVariableValue("icon", icon); + if (resourceManager.TryLoadIcon(icon, out var iconResource)) + appearance.Icon = iconResource.Id; + } - if (!invisibility.IsNull) { - obj.SetVariableValue("invisibility", invisibility); - invisibility.TryGetValueAsInteger(out var invisibilityValue); - appearance.Invisibility = (sbyte)Math.Clamp(invisibilityValue, -127, 127); - } + if (!iconState.IsNull) { + obj.SetVariableValue("icon_state", iconState); + iconState.TryGetValueAsString(out appearance.IconState); + } - /* TODO suffix - if (!suffix.IsNull) { - obj.SetVariableValue("suffix", suffix); - suffix.TryGetValueAsString(out appearance.Suffix); - } - */ - }); + if (!invisibility.IsNull) { + obj.SetVariableValue("invisibility", invisibility); + invisibility.TryGetValueAsInteger(out var invisibilityValue); + appearance.Invisibility = (sbyte)Math.Clamp(invisibilityValue, -127, 127); + } - state.Push(DreamValue.Null); - return ProcStatus.Continue; + /* TODO suffix + if (!suffix.IsNull) { + obj.SetVariableValue("suffix", suffix); + suffix.TryGetValueAsString(out appearance.Suffix); + } + */ + }); + + state.Push(DreamValue.Null); + return ProcStatus.Continue; + } finally { + foreach (var argument in argumentValues) + argument.Dispose(); + } } public static ProcStatus LocateCoord(DMProcState state) { - var z = (int)state.Pop().UnsafeGetValueAsFloat(); - var y = (int)state.Pop().UnsafeGetValueAsFloat(); - var x = (int)state.Pop().UnsafeGetValueAsFloat(); + var z = (int)state.UnsafePopAsFloat(); + var y = (int)state.UnsafePopAsFloat(); + var x = (int)state.UnsafePopAsFloat(); state.Proc.DreamMapManager.TryGetTurfAt((x, y), z, out var turf); state.Push(new DreamValue(turf)); @@ -2400,12 +2538,13 @@ public static ProcStatus LocateCoord(DMProcState state) { } public static ProcStatus Locate(DMProcState state) { - if (!state.Pop().TryGetValueAsDreamObject(out var container)) { + using var containerStack = state.Pop(); + if (!containerStack.TryGetValueAsDreamObject(out var container)) { state.Push(DreamValue.Null); return ProcStatus.Continue; } - DreamValue value = state.Pop(); + using var value = state.Pop(); // Enumerate atoms rather than creating a list of every /atom using WorldContentsList.GetValues() if (container is DreamObjectWorld && value.Type != DreamValue.DreamValueType.String) { @@ -2423,18 +2562,21 @@ public static ProcStatus Locate(DMProcState state) { DreamList? containerList; if (container is DreamObjectAtom) { - container.GetVariable("contents").TryGetValueAsDreamList(out containerList); + using var contents = container.GetVariable("contents"); + + contents.TryGetValueAsDreamList(out containerList); } else { containerList = container as DreamList; } if (value.TryGetValueAsString(out var refString)) { - var refValue = state.Proc.RefManager.LocateRef(refString); + using var refValue = state.Proc.RefManager.LocateRef(refString); + if(container is not DreamObjectWorld && containerList is not null) { //if it's a valid ref, it's in world, we don't need to check state.Push(containerList.ContainsValue(refValue) ? refValue : DreamValue.Null); - return ProcStatus.Continue; - } else + } else { state.Push(refValue); + } } else if (value.TryGetValueAsType(out var ancestor)) { if (containerList == null) { state.Push(DreamValue.Null); @@ -2468,7 +2610,6 @@ public static ProcStatus Locate(DMProcState state) { } state.Push(containerList.ContainsValue(value) ? value : DreamValue.Null); - return ProcStatus.Continue; } return ProcStatus.Continue; @@ -2480,11 +2621,10 @@ public static ProcStatus PickWeighted(DMProcState state) { (DreamValue Value, float CumulativeWeight)[] values = new (DreamValue, float)[count]; float totalWeight = 0; for (int i = 0; i < count; i++) { - DreamValue value = state.Pop(); - if (!state.Pop().TryGetValueAsFloat(out var weight)) - { + using var value = state.Pop(); + using var weightStack = state.Pop(); + if (!weightStack.TryGetValueAsFloat(out var weight)) weight = 100; - } totalWeight += weight; values[i] = (value, totalWeight); @@ -2504,9 +2644,8 @@ public static ProcStatus PickWeighted(DMProcState state) { public static ProcStatus PickUnweighted(DMProcState state) { int count = state.ReadInt(); - DreamValue picked; if (count == 1) { - DreamValue value = state.Pop(); + using var value = state.Pop(); List values; if (value.TryGetValueAsDreamList(out var list)) { @@ -2519,19 +2658,21 @@ public static ProcStatus PickUnweighted(DMProcState state) { if (values.Count == 0) throw new Exception("pick() from empty list"); - picked = values[state.DreamManager.Random.Next(0, values.Count)]; + state.Push(values[state.DreamManager.Random.Next(0, values.Count)]); } else { int pickedIndex = state.DreamManager.Random.Next(0, count); + var possibleValues = state.PopCount(count); - picked = state.PopCount(count)[pickedIndex]; + state.Push(possibleValues[pickedIndex]); + foreach (var value in possibleValues) + value.Dispose(); } - state.Push(picked); return ProcStatus.Continue; } public static ProcStatus Prob(DMProcState state) { - DreamValue probability = state.Pop(); + using var probability = state.Pop(); if (probability.TryGetValueAsFloat(out float probabilityValue)) { int result = (state.DreamManager.Random.Prob(probabilityValue / 100)) ? 1 : 0; @@ -2545,8 +2686,8 @@ public static ProcStatus Prob(DMProcState state) { } public static ProcStatus IsSaved(DMProcState state) { - DreamValue key = state.Pop(); - DreamValue owner = state.Pop(); + using var key = state.Pop(); + using var owner = state.Pop(); // number indices always evaluate to false here if (key.TryGetValueAsFloat(out _)) { @@ -2604,7 +2745,7 @@ private static void PerformOutput(DreamValue a, DreamValue b) { public static ProcStatus OutputReference(DMProcState state) { DreamReference leftRef = state.ReadReference(); - DreamValue right = state.Pop(); + using var right = state.Pop(); if (leftRef.Type == DMReference.Type.ListIndex) { state.GetIndexReferenceValues(leftRef, out _, out var indexing, peek: true); @@ -2618,13 +2759,14 @@ public static ProcStatus OutputReference(DMProcState state) { } } - PerformOutput(state.GetReferenceValue(leftRef), right); + using var value = state.GetReferenceValue(leftRef); + PerformOutput(value, right); return ProcStatus.Continue; } public static ProcStatus Output(DMProcState state) { - DreamValue right = state.Pop(); - DreamValue left = state.Pop(); + using var right = state.Pop(); + using var left = state.Pop(); PerformOutput(left, right); return ProcStatus.Continue; @@ -2640,28 +2782,36 @@ public static ProcStatus Input(DMProcState state) { if (indexing.TryGetValueAsDreamObject(out _)) { // Savefiles get some special treatment. // "savefile[A] >> B" is the same as "B = savefile[A]" - state.AssignReference(rightRef, state.GetReferenceValue(leftRef)); return ProcStatus.Continue; } else { // Pop the reference's stack values - state.GetReferenceValue(leftRef); - state.GetReferenceValue(rightRef); + state.PopReference(leftRef); + state.PopReference(rightRef); + } + } else { + using var leftValue = state.GetReferenceValue(leftRef); + + if (leftValue.TryGetValueAsDreamObject(out var savefile)) { + // Savefiles get some special treatment. + // "savefile >> B" is the same as "B = savefile[current_dir]" + using var result = savefile.OperatorInput(); + + state.AssignReference(rightRef, result); + return ProcStatus.Continue; } - } else if (state.GetReferenceValue(leftRef).TryGetValueAsDreamObject(out var savefile)) { - // Savefiles get some special treatment. - // "savefile >> B" is the same as "B = savefile[current_dir]" - state.AssignReference(rightRef, savefile.OperatorInput()); - return ProcStatus.Continue; } throw new NotImplementedException($"Input operation is unimplemented for {leftRef} and {rightRef}"); } public static ProcStatus Browse(DMProcState state) { - state.Pop().TryGetValueAsString(out string? options); - DreamValue body = state.Pop(); - if (!state.Pop().TryGetValueAsDreamObject(out var receiver) || receiver == null) + using var optionsStack = state.Pop(); + using var bodyStack = state.Pop(); + using var receiverStack = state.Pop(); + + optionsStack.TryGetValueAsString(out var options); + if (!receiverStack.TryGetValueAsDreamObject(out var receiver)) return ProcStatus.Continue; IEnumerable clients; @@ -2676,12 +2826,12 @@ public static ProcStatus Browse(DMProcState state) { } string? browseValue; - if (body.TryGetValueAsDreamResource(out var resource)) { + if (bodyStack.TryGetValueAsDreamResource(out var resource)) { browseValue = resource.ReadAsString(); - } else if (body.TryGetValueAsString(out browseValue) || body.IsNull) { + } else if (bodyStack.TryGetValueAsString(out browseValue) || bodyStack.IsNull) { // Got it. } else { - throw new Exception($"Invalid browse() body: expected resource or string, got {body}"); + throw new Exception($"Invalid browse() body: expected resource or string, got {bodyStack}"); } foreach (DreamConnection client in clients) { @@ -2692,8 +2842,9 @@ public static ProcStatus Browse(DMProcState state) { } public static ProcStatus BrowseResource(DMProcState state) { - DreamValue filename = state.Pop(); - var value = state.Pop(); + using var filename = state.Pop(); + using var value = state.Pop(); + using var receiverStack = state.Pop(); if (!value.TryGetValueAsDreamResource(out var file)) { if (state.Proc.DreamResourceManager.TryLoadIcon(value, out var icon)) { @@ -2703,7 +2854,7 @@ public static ProcStatus BrowseResource(DMProcState state) { } } - if (!state.Pop().TryGetValueAsDreamObject(out var receiver) || receiver == null) + if (!receiverStack.TryGetValueAsDreamObject(out var receiver)) return ProcStatus.Continue; DreamConnection? connection; @@ -2720,9 +2871,9 @@ public static ProcStatus BrowseResource(DMProcState state) { } public static ProcStatus DeleteObject(DMProcState state) { - state.Pop().TryGetValueAsDreamObject(out var dreamObject); + using var dreamObjectStack = state.Pop(); - if (dreamObject is not null) { + if (dreamObjectStack.TryGetValueAsDreamObject(out var dreamObject)) { dreamObject.Delete(); if (dreamObject == state.Instance) // We just deleted our src, end the proc TODO: Is the entire thread cancelled? @@ -2733,9 +2884,12 @@ public static ProcStatus DeleteObject(DMProcState state) { } public static ProcStatus OutputControl(DMProcState state) { - string control = state.Pop().GetValueAsString(); - string message = state.Pop().Stringify(); - if (!state.Pop().TryGetValueAsDreamObject(out var receiver) || receiver == null) + using var controlStack = state.Pop(); + using var messageStack = state.Pop(); + using var receiverStack = state.Pop(); + string control = controlStack.GetValueAsString(); + string message = messageStack.Stringify(); + if (!receiverStack.TryGetValueAsDreamObject(out var receiver)) return ProcStatus.Continue; // TODO: When errors are more strict (or a setting for it added), a null receiver should error @@ -2770,22 +2924,25 @@ public static ProcStatus OutputControl(DMProcState state) { public static ProcStatus Prompt(DMProcState state) { DreamValueType types = (DreamValueType)state.ReadInt(); - DreamValue list = state.Pop(); + using var list = state.Pop(); DreamValue message, title, defaultValue; - DreamValue firstArg = state.Pop(); - firstArg.TryGetValueAsDreamObject(out var recipient); + using var arg1 = state.Pop(); + using var arg2 = state.Pop(); + using var arg3 = state.Pop(); + using var arg4 = state.Pop(); + arg1.TryGetValueAsDreamObject(out var recipient); if (recipient is DreamObjectMob or DreamObjectClient) { - message = state.Pop(); - title = state.Pop(); - defaultValue = state.Pop(); + message = arg2; + title = arg3; + defaultValue = arg4; } else { recipient = state.Usr; - message = firstArg; - title = state.Pop(); - defaultValue = state.Pop(); - state.PopDrop(); //Fourth argument, should be null + message = arg1; + title = arg2; + defaultValue = arg3; + // arg4 isn't used, and should be null } DreamConnection? connection = null; @@ -2813,8 +2970,9 @@ public static ProcStatus Prompt(DMProcState state) { } public static ProcStatus Link(DMProcState state) { - DreamValue url = state.Pop(); - if (!state.Pop().TryGetValueAsDreamObject(out var receiver) || receiver == null) + using var url = state.Pop(); + using var receiverStack = state.Pop(); + if (!receiverStack.TryGetValueAsDreamObject(out var receiver)) return ProcStatus.Continue; DreamConnection? connection = receiver switch { @@ -2834,9 +2992,10 @@ public static ProcStatus Link(DMProcState state) { } public static ProcStatus Ftp(DMProcState state) { - DreamValue name = state.Pop(); - DreamValue file = state.Pop(); - if (!state.Pop().TryGetValueAsDreamObject(out var receiver) || receiver == null) + using var name = state.Pop(); + using var file = state.Pop(); + using var receiverStack = state.Pop(); + if (!receiverStack.TryGetValueAsDreamObject(out var receiver)) return ProcStatus.Continue; DreamConnection? connection; @@ -2891,6 +3050,8 @@ public static ProcStatus MassConcatenation(DMProcState state) { if (add.TryGetValueAsString(out var addStr)) { builder.Append(addStr); } + + add.Dispose(); } state.Push(new DreamValue(builder.ToString())); @@ -2898,20 +3059,20 @@ public static ProcStatus MassConcatenation(DMProcState state) { } public static ProcStatus DereferenceIndex(DMProcState state) { - DreamValue index = state.Pop(); - DreamValue obj = state.Pop(); + using var index = state.Pop(); + using var obj = state.Pop(); + using var indexResult = state.GetIndex(obj, index, state); - var indexResult = state.GetIndex(obj, index, state); state.Push(indexResult); return ProcStatus.Continue; } public static ProcStatus IndexRefWithString(DMProcState state) { - DreamReference reference = state.ReadReference(); - var refValue = state.GetReferenceValue(reference); + var reference = state.ReadReference(); + using var refValue = state.GetReferenceValue(reference); var index = new DreamValue(state.ReadString()); - var indexResult = state.GetIndex(refValue, index, state); + using var indexResult = state.GetIndex(refValue, index, state); state.Push(indexResult); return ProcStatus.Continue; @@ -2921,7 +3082,7 @@ public static ProcStatus DereferenceCall(DMProcState state) { string name = state.ReadString(); var argumentInfo = state.ReadProcArguments(); var argumentValues = state.PopCount(argumentInfo.StackSize); - DreamValue obj = state.Pop(); + using var obj = state.Pop(); if (!obj.TryGetValueAsDreamObject(out var instance) || instance == null) throw new Exception($"Cannot dereference proc \"{name}\" from {obj}"); @@ -2929,7 +3090,6 @@ public static ProcStatus DereferenceCall(DMProcState state) { throw new Exception($"Type {instance.ObjectDefinition.Type} has no proc called \"{name}\""); var arguments = state.CreateProcArguments(argumentValues, proc, argumentInfo.Type, argumentInfo.StackSize); - return state.Call(proc, instance, arguments); } @@ -3021,8 +3181,10 @@ public static bool IsEqual(DreamValue first, DreamValue second) { } private static bool IsEquivalent(DreamValue first, DreamValue second) { - if (first.TryGetValueAsDreamObject(out var firstObject) && firstObject != null) { - return firstObject.OperatorEquivalent(second).IsTruthy(); + if (first.TryGetValueAsDreamObject(out var firstObject)) { + using var opResult = firstObject.OperatorEquivalent(second); + + return opResult.IsTruthy(); } // Behaviour is otherwise equivalent (pun intended) to == @@ -3069,15 +3231,16 @@ private static bool IsLessThan(DreamValue first, DreamValue second) { } } + [MustDisposeResource] private static DreamValue BitXorValues(DreamObjectTree objectTree, DreamValue first, DreamValue second) { if (first.TryGetValueAsDreamList(out var list)) { DreamList newList = objectTree.CreateList(); List values; - if (second.TryGetValueAsDreamList(out DreamList secondList)) { + if (second.TryGetValueAsDreamList(out var secondList)) { values = secondList.GetValues(); } else { - values = new List() { second }; + values = new List { second }; } foreach (DreamValue value in values) { @@ -3087,7 +3250,7 @@ private static DreamValue BitXorValues(DreamObjectTree objectTree, DreamValue fi if (inFirstList ^ inSecondList) { newList.AddValue(value); - DreamValue associatedValue = inFirstList ? list.GetValue(value) : secondList.GetValue(value); + using var associatedValue = inFirstList ? list.GetValue(value) : secondList.GetValue(value); if (!associatedValue.IsNull) newList.SetValue(value, associatedValue); } } @@ -3272,8 +3435,10 @@ private static DreamValue IconOperationAdd(DMProcState state, DreamValue icon, D // Create a new /icon and ICON_ADD blend it // Note that BYOND creates something other than an /icon, but it behaves the same as one in most reasonable interactions var iconObj = state.Proc.ObjectTree.CreateObject(state.Proc.ObjectTree.Icon); - if (!state.Proc.DreamResourceManager.TryLoadIcon(icon, out var from)) + if (!state.Proc.DreamResourceManager.TryLoadIcon(icon, out var from)) { + iconObj.DecRef(); throw new Exception($"Failed to create an icon from {from}"); + } iconObj.Icon.InsertStates(from, DreamValue.Null, DreamValue.Null, DreamValue.Null); DreamProcNativeIcon.Blend(iconObj.Icon, blend, DreamIconOperationBlend.BlendType.Add, 0, 0); @@ -3323,19 +3488,19 @@ public static ProcStatus NullRef(DMProcState state) { public static ProcStatus AssignNoPush(DMProcState state) { DreamReference reference = state.ReadReference(); - DreamValue value = state.Pop(); + using var value = state.Pop(); state.AssignReference(reference, value); return ProcStatus.Continue; } public static ProcStatus PushReferenceAndDereferenceField(DMProcState state) { - DreamReference reference = state.ReadReference(); - string fieldName = state.ReadString(); - - DreamValue owner = state.GetReferenceValue(reference); - state.Push(state.DereferenceField(owner, fieldName)); + var reference = state.ReadReference(); + var fieldName = state.ReadString(); + using var owner = state.GetReferenceValue(reference); + using var value = state.DereferenceField(owner, fieldName); + state.Push(value); return ProcStatus.Continue; } @@ -3367,9 +3532,10 @@ public static ProcStatus PushNRefs(DMProcState state) { int count = state.ReadInt(); for (int i = 0; i < count; i++) { - DreamReference reference = state.ReadReference(); + var reference = state.ReadReference(); + using var value = state.GetReferenceValue(reference); - state.Push(state.GetReferenceValue(reference)); + state.Push(value); } return ProcStatus.Continue; @@ -3397,10 +3563,9 @@ public static ProcStatus PushStringFloat(DMProcState state) { } public static ProcStatus JumpIfReferenceFalse(DMProcState state) { - DreamReference reference = state.ReadReference(); - int jumpTo = state.ReadInt(); - - DreamValue value = state.GetReferenceValue(reference); + var reference = state.ReadReference(); + var jumpTo = state.ReadInt(); + using var value = state.GetReferenceValue(reference); if (!value.IsTruthy()) { state.Jump(jumpTo); @@ -3412,7 +3577,7 @@ public static ProcStatus JumpIfReferenceFalse(DMProcState state) { public static ProcStatus SwitchOnFloat(DMProcState state) { float testValue = state.ReadFloat(); int casePosition = state.ReadInt(); - var test = state.Pop(); + using var test = state.Pop(); if (test.TryGetValueAsFloat(out var value)) { if (testValue.Equals(value)) { state.Jump(casePosition); @@ -3429,7 +3594,7 @@ public static ProcStatus SwitchOnFloat(DMProcState state) { public static ProcStatus SwitchOnString(DMProcState state) { string testValue = state.ReadString(); int casePosition = state.ReadInt(); - var test = state.Pop(); + using var test = state.Pop(); if (test.TryGetValueAsString(out var value)) { if (testValue.Equals(value)) { state.Jump(casePosition); @@ -3487,6 +3652,7 @@ public static ProcStatus CreateListNFloats(DMProcState state) { } state.Push(new DreamValue(list)); + list.DecRef(); return ProcStatus.Continue; } @@ -3501,6 +3667,7 @@ public static ProcStatus CreateListNStrings(DMProcState state) { } state.Push(new DreamValue(list)); + list.DecRef(); return ProcStatus.Continue; } @@ -3509,12 +3676,14 @@ public static ProcStatus CreateListNRefs(DMProcState state) { var list = state.Proc.ObjectTree.CreateList(size); for (int i = 0; i < size; i++) { - DreamReference reference = state.ReadReference(); + var reference = state.ReadReference(); + using var value = state.GetReferenceValue(reference); - list.AddValue(state.GetReferenceValue(reference)); + list.AddValue(value); } state.Push(new DreamValue(list)); + list.DecRef(); return ProcStatus.Continue; } @@ -3528,11 +3697,12 @@ public static ProcStatus CreateListNResources(DMProcState state) { } state.Push(new DreamValue(list)); + list.DecRef(); return ProcStatus.Continue; } public static ProcStatus IsTypeDirect(DMProcState state) { - DreamValue value = state.Pop(); + using var value = state.Pop(); int typeId = state.ReadInt(); var typeValue = state.Proc.ObjectTree.Types[typeId]; diff --git a/OpenDreamRuntime/Procs/DMProc.cs b/OpenDreamRuntime/Procs/DMProc.cs index 07e46054bf..200bc5a57b 100644 --- a/OpenDreamRuntime/Procs/DMProc.cs +++ b/OpenDreamRuntime/Procs/DMProc.cs @@ -5,6 +5,7 @@ using DMCompiler.Bytecode; using DMCompiler.DM; using DMCompiler.Json; +using JetBrains.Annotations; using OpenDreamRuntime.Map; using OpenDreamRuntime.Objects; using OpenDreamRuntime.Objects.Types; @@ -21,6 +22,7 @@ public sealed class DMProc : DreamProc { public readonly bool IsNullProc; public IReadOnlyList LocalNames { get; } public readonly List SourceInfo; + public int LocalCount => LocalNames.Count; public readonly AtomManager AtomManager; public readonly DreamManager DreamManager; @@ -105,13 +107,14 @@ public bool TryGetOffsetAtSource(string source, int line, out int offset) { return false; } - public override ProcState CreateState(DreamThread thread, DreamObject? src, DreamObject? usr, DreamProcArguments arguments) { + public override ProcState CreateState(DreamThread thread, DreamObject? src, DreamObject? usr, [HandlesResourceDisposal] DreamProcArguments arguments) { if (IsNullProc) { if (!NullProcState.Pool.TryPop(out var nullState)) { nullState = new NullProcState(); } nullState.Initialize(this); + arguments.Dispose(); return nullState; } @@ -378,7 +381,7 @@ public sealed class DMProcState : ProcState { private readonly Stack _catchVarIndex = new(); /// Contains both arguments (at index 0) and local vars (at index ArgumentCount) - private DreamValue[] _localVariables = default!; + private readonly DreamValue[] _localVariables = new DreamValue[256]; /// Static initializer for maintainer friendly OpcodeHandlers to performance friendly _opcodeHandlers static unsafe DMProcState() { @@ -404,42 +407,51 @@ private DMProcState(DMProcState other, DreamThread thread) { base.Initialize(thread, other.WaitFor); _proc = other._proc; Instance = other.Instance; + Instance?.IncRef(); Usr = other.Usr; + Usr?.IncRef(); ArgumentCount = other.ArgumentCount; _pc = other._pc; _firstResume = false; Result = other.Result; + Result.IncRef(); _stack = DreamValuePool.Rent(other._stack.Length); - _localVariables = DreamValuePool.Rent(other._localVariables.Length); - Array.Copy(other._localVariables, _localVariables, other._localVariables.Length); + + Array.Copy(other._localVariables, _localVariables, ArgumentCount + _proc.LocalCount); + for (int i = 0; i < ArgumentCount + _proc.LocalCount; i++) + _localVariables[i].IncRef(); } - public void Initialize(DMProc proc, DreamThread thread, int maxStackSize, DreamObject? instance, DreamObject? usr, DreamProcArguments arguments) { + public void Initialize(DMProc proc, DreamThread thread, int maxStackSize, DreamObject? instance, DreamObject? usr, [HandlesResourceDisposal] DreamProcArguments arguments) { base.Initialize(thread, (proc.Attributes & ProcAttributes.DisableWaitfor) != ProcAttributes.DisableWaitfor); _proc = proc; Instance = instance; + Instance?.IncRef(); Usr = usr; + Usr?.IncRef(); ArgumentCount = Math.Max(arguments.Count, _proc.ArgumentNames?.Count ?? 0); - _localVariables = DreamValuePool.Rent(256); _stack = DreamValuePool.Rent(maxStackSize); _firstResume = true; for (int i = 0; i < ArgumentCount; i++) { - _localVariables[i] = arguments.GetArgument(i); + SetArgument(i, arguments.GetArgument(i)); } + + arguments.Dispose(); } public override unsafe ProcStatus Resume() { if (Instance?.Deleted == true) { + Instance = null; return ProcStatus.Returned; } #if TOOLS - if (_firstResume) { - DebugManager.HandleFirstResume(this); - _firstResume = false; - } + if (_firstResume) { + DebugManager.HandleFirstResume(this); + _firstResume = false; + } #endif var procBytecode = _proc.Bytecode; @@ -453,7 +465,7 @@ public override unsafe ProcStatus Resume() { while (_pc < l) { #if TOOLS - DebugManager.HandleInstruction(this); + DebugManager.HandleInstruction(this); #endif int opcode = bytecode[_pc]; @@ -501,23 +513,33 @@ public void Jump(int position) { } public void SetReturn(DreamValue value) { + value.IncRef(); + Result.DecRef(); Result = value; } - public ProcStatus Call(DreamProc proc, DreamObject? src, DreamProcArguments arguments) { - if (proc is NativeProc p) { + public ProcStatus Call(DreamProc proc, DreamObject? src, [HandlesResourceDisposal] DreamProcArguments arguments) { + if (proc is NativeProc p) { // Skip a whole song and dance. // ReSharper disable ExplicitCallerInfoArgument - using(Profiler.BeginZone(filePath:"Native Proc", lineNumber:0, memberName:p.Name)) - // Skip a whole song and dance. - Push(p.Call(Thread, src, Usr, arguments)); + using (Profiler.BeginZone(filePath: "Native Proc", lineNumber: 0, memberName: p.Name)) { + using var result = p.Call(Thread, src, Usr, arguments); + + Push(result); + } + // ReSharper restore ExplicitCallerInfoArgument return ProcStatus.Continue; } var state = proc.CreateState(Thread, src, Usr, arguments); Thread.PushProcState(state); - if (proc is AsyncNativeProc) // Hack to ensure sleeping native procs will return our value in a no-waitfor context + if (proc is AsyncNativeProc) { + // Hack to ensure sleeping native procs will return our value in a no-waitfor context + state.Result.DecRef(); + Result.IncRef(); state.Result = Result; + } + return ProcStatus.Called; } @@ -554,11 +576,16 @@ public override void CatchException(Exception exception) { if (varIdx != NoTryCatchVar) { DreamValue value; - if (exception is DMThrowException throwException) + if (exception is DMThrowException throwException) { + // DMThrowException incremements its Value's ref count + // Let's consider this an ownership transfer, so no IncRef/DecRef is needed value = throwException.Value; - else + } else { value = new DreamValue(exception.Message); // TODO: Probably need to create an /exception + value.IncRef(); + } + _localVariables[varIdx].DecRef(); _localVariables[varIdx] = value; } } @@ -566,10 +593,20 @@ public override void CatchException(Exception exception) { public override void Dispose() { base.Dispose(); + for (int i = 0; i < ArgumentCount + _proc.LocalCount; i++) + _localVariables[i].Dispose(); + for (int i = --_stackIndex; i >= 0; i--) + _stack[i].Dispose(); + foreach (var enumerator in Enumerators) + enumerator?.Dispose(); + + Instance?.DecRef(); Instance = null; + Usr?.DecRef(); Usr = null; - ArgumentCount = 0; Array.Clear(Enumerators); + Array.Clear(_localVariables, 0, ArgumentCount + _proc.LocalCount); + ArgumentCount = 0; _pc = 0; _proc = null!; @@ -577,9 +614,6 @@ public override void Dispose() { _stackIndex = 0; _stack = null!; - DreamValuePool.Return(_localVariables, true); - _localVariables = null!; - _catchPosition.Clear(); _catchVarIndex.Clear(); @@ -594,6 +628,16 @@ public override void SetArgument(int id, DreamValue value) { if (id < 0 || id >= ArgumentCount) throw new IndexOutOfRangeException($"Given argument id ({id}) was out of range"); + value.IncRef(); + _localVariables[id].Dispose(); + _localVariables[id] = value; + } + + public void SetLocal(int id, DreamValue value) { + id += ArgumentCount; // Arguments take up the first local var slots + + value.IncRef(); + _localVariables[id].Dispose(); _localVariables[id] = value; } @@ -605,19 +649,29 @@ public override void SetArgument(int id, DreamValue value) { public void Push(DreamValue value) { _stack[_stackIndex] = value; + value.IncRef(); + // ++ sucks for the compiler _stackIndex += 1; } + [MustDisposeResource] public DreamValue Pop() { // -- sucks for the compiler _stackIndex -= 1; return _stack[_stackIndex]; } + public float UnsafePopAsFloat() { + _stackIndex -= 1; + _stack[_stackIndex].DecRef(); + return _stack[_stackIndex].UnsafeGetValueAsFloat(); + } + public void PopDrop() { DebugTools.Assert(_stackIndex > 0, "Attempted to PopDrop with a stack index of (or below?) 0"); _stackIndex -= 1; + _stack[_stackIndex].DecRef(); } /// @@ -642,6 +696,7 @@ public DreamValue Peek() { /// The source of the arguments /// The amount of items the arguments have on the stack /// The arguments in a DreamProcArguments struct + [MustDisposeResource] public DreamProcArguments PopProcArguments(DreamProc? proc, DMCallArgumentsType argumentsType, int argumentStackSize) { var values = PopCount(argumentStackSize); @@ -756,42 +811,57 @@ private static void ThrowReferenceNotListIndex() { public void AssignReference(DreamReference reference, DreamValue value, bool peek = false) { switch (reference.Type) { case DMReference.Type.NoRef: break; - case DMReference.Type.Self: Result = value; break; + case DMReference.Type.Self: SetReturn(value); break; case DMReference.Type.Argument: SetArgument(reference.Value, value); break; - case DMReference.Type.Local: _localVariables[ArgumentCount + reference.Value] = value; break; + case DMReference.Type.Local: SetLocal(reference.Value, value); break; case DMReference.Type.SrcField: Instance.SetVariable(ResolveString(reference.Value), value); break; - case DMReference.Type.Global: DreamManager.Globals[reference.Value] = value; break; + case DMReference.Type.Global: DreamManager.SetGlobal(reference.Value, value); break; case DMReference.Type.Src: + Instance?.DecRef(); + //TODO: src can be assigned to non-DreamObject values if (!value.TryGetValueAsDreamObject(out Instance)) { ThrowCannotAssignSrcTo(value); } + Instance?.IncRef(); break; case DMReference.Type.Usr: + Usr?.DecRef(); + //TODO: usr can be assigned to non-DreamObject values if (!value.TryGetValueAsDreamObject(out Usr)) { ThrowCannotAssignUsrTo(value); } + Usr?.IncRef(); break; case DMReference.Type.Field: { - DreamValue owner = peek ? Peek() : Pop(); + var owner = peek ? Peek() : Pop(); if (!owner.TryGetValueAsDreamObject(out var ownerObj) || ownerObj == null) ThrowCannotAssignFieldOn(reference, owner); ownerObj!.SetVariable(ResolveString(reference.Value), value); + if (!peek) + owner.Dispose(); break; } case DMReference.Type.ListIndex: { GetIndexReferenceValues(reference, out var index, out var indexing, peek); - if (indexing.TryGetValueAsIDreamList(out var dreamList)) { - dreamList.SetValue(index, value); - } else if (indexing.TryGetValueAsDreamObject(out var dreamObject)) { - dreamObject.OperatorIndexAssign(index, this, value); - } else { - ThrowCannotAssignListIndex(index, indexing); + try { + if (indexing.TryGetValueAsIDreamList(out var dreamList)) { + dreamList.SetValue(index, value); + } else if (indexing.TryGetValueAsDreamObject(out var dreamObject)) { + dreamObject.OperatorIndexAssign(index, this, value); + } else { + ThrowCannotAssignListIndex(index, indexing); + } + } finally { + if (!peek) { + index.Dispose(); + indexing.Dispose(); + } } break; @@ -827,17 +897,39 @@ private static void ThrowCannotAssignUsrTo(DreamValue value) { throw new Exception($"Cannot assign usr to {value}"); } + [MustDisposeResource] public DreamValue GetReferenceValue(DreamReference reference, bool peek = false) { switch (reference.Type) { case DMReference.Type.NoRef: return DreamValue.Null; - case DMReference.Type.Src: return new(Instance); - case DMReference.Type.Usr: return new(Usr); - case DMReference.Type.Self: return Result; - case DMReference.Type.Global: return DreamManager.Globals[reference.Value]; - case DMReference.Type.Argument: return _localVariables[reference.Value]; - case DMReference.Type.Local: return _localVariables[ArgumentCount + reference.Value]; - case DMReference.Type.Args: return new(new ProcArgsList(Proc.ObjectTree.List.ObjectDefinition, this)); - case DMReference.Type.World: return new(DreamManager.WorldInstance); + case DMReference.Type.Src: + Instance?.IncRef(); + return new(Instance); + case DMReference.Type.Usr: + Usr?.IncRef(); + return new(Usr); + case DMReference.Type.Self: + Result.IncRef(); + return Result; + case DMReference.Type.Global: + var global = DreamManager.Globals[reference.Value]; + + global.IncRef(); + return global; + case DMReference.Type.Argument: + var argument = _localVariables[reference.Value]; + + argument.IncRef(); + return _localVariables[reference.Value]; + case DMReference.Type.Local: + var local = _localVariables[ArgumentCount + reference.Value]; + + local.IncRef(); + return local; + case DMReference.Type.Args: + return new(new ProcArgsList(Proc.ObjectTree.List.ObjectDefinition, this)); + case DMReference.Type.World: + DreamManager.WorldInstance.IncRef(); + return new(DreamManager.WorldInstance); case DMReference.Type.Callee: { // TODO: BYOND seems to reuse the same object. At least, callee == callee var callee = Proc.ObjectTree.CreateObject(Proc.ObjectTree.Callee); @@ -851,9 +943,12 @@ public DreamValue GetReferenceValue(DreamReference reference, bool peek = false) return DreamValue.Null; } case DMReference.Type.Field: { - DreamValue owner = peek ? Peek() : Pop(); + var owner = peek ? Peek() : Pop(); + var fieldValue = DereferenceField(owner, ResolveString(reference.Value)); - return DereferenceField(owner, ResolveString(reference.Value)); + if (!peek) + owner.Dispose(); + return fieldValue; } case DMReference.Type.SrcField: { var fieldName = ResolveString(reference.Value); @@ -867,7 +962,13 @@ public DreamValue GetReferenceValue(DreamReference reference, bool peek = false) case DMReference.Type.ListIndex: { GetIndexReferenceValues(reference, out var index, out var indexing, peek); - return GetIndex(indexing, index, this); + var value = GetIndex(indexing, index, this); + if (!peek) { + index.Dispose(); + indexing.Dispose(); + } + + return value; } default: ThrowCannotGetValueOfReferenceType(reference); @@ -923,6 +1024,7 @@ private static void ThrowPopInvalidType(DMReference.Type type) { throw new Exception($"Cannot pop stack values of reference type {type}"); } + [MustDisposeResource] public DreamValue DereferenceField(DreamValue owner, string field) { if (owner.TryGetValueAsDreamObject(out var ownerObj)) { if (!ownerObj.TryGetVariable(field, out var fieldValue)) @@ -959,6 +1061,7 @@ private static void ThrowTypeHasNoField(string field, DreamObject ownerObj) { throw new Exception($"Type {ownerObj.ObjectDefinition.Type} has no field called \"{field}\""); } + [MustDisposeResource] public DreamValue GetIndex(DreamValue indexing, DreamValue index, DMProcState state) { if (indexing.TryGetValueAsDreamList(out var listObj)) { return listObj.GetValue(index); @@ -972,10 +1075,8 @@ public DreamValue GetIndex(DreamValue indexing, DreamValue index, DMProcState st return new DreamValue(Convert.ToString(c)); } - if (indexing.TryGetValueAsDreamObject(out var dreamObject)) { - if (dreamObject != null) { - return dreamObject.OperatorIndex(index, state); - } + if (indexing.TryGetValueAsDreamObject(out var dreamObject)) { + return dreamObject.OperatorIndex(index, state); } ThrowCannotGetIndex(indexing, index); @@ -1039,13 +1140,22 @@ private static void ThrowAttemptedToIndexString(DreamValue index) { // being there, so just stop after the named locals. } - public DreamProcArguments CreateProcArguments(ReadOnlySpan values, DreamProc? proc, DMCallArgumentsType argumentsType, int argumentStackSize) { + [MustDisposeResource] + public DreamProcArguments CreateProcArguments([HandlesResourceDisposal] ReadOnlySpan values, DreamProc? proc, DMCallArgumentsType argumentsType, int argumentStackSize) { switch (argumentsType) { case DMCallArgumentsType.None: + foreach (var value in values) + value.Dispose(); + return new DreamProcArguments(); case DMCallArgumentsType.FromStack: - return new DreamProcArguments(values); + var fromStackArgs = new DreamProcArguments(values); + foreach (var value in values) + value.Dispose(); + + return fromStackArgs; case DMCallArgumentsType.FromProcArguments: + DebugTools.Assert(values.Length == 0); return new DreamProcArguments(GetArguments()); case DMCallArgumentsType.FromStackKeyed: { if (argumentStackSize % 2 != 0) @@ -1065,8 +1175,8 @@ public DreamProcArguments CreateProcArguments(ReadOnlySpan values, D Array.Fill(arguments, DreamValue.Null); for (int i = 0; i < argumentCount; i++) { - var key = values[i*2]; - var value = values[i*2+1]; + var key = values[i * 2]; + var value = values[i * 2 + 1]; if (key.IsNull) { // image() or new /image() will skip the loc arg if the second arg is a string @@ -1088,7 +1198,11 @@ public DreamProcArguments CreateProcArguments(ReadOnlySpan values, D } } - return new DreamProcArguments(arguments); + var procArgs = new DreamProcArguments(arguments); + foreach (var value in values) + value.Dispose(); + + return procArgs; } case DMCallArgumentsType.FromArgumentList: { if (proc == null) @@ -1129,10 +1243,17 @@ public DreamProcArguments CreateProcArguments(ReadOnlySpan values, D // TODO: Verify ordered args precede all named args arguments[skippingArg ? i + 1 : i] = value; + value.IncRef(); } } - return new DreamProcArguments(arguments); + var procArgs = new DreamProcArguments(arguments); + foreach (var arg in arguments) + arg.Dispose(); + foreach (var value in values) + value.Dispose(); + + return procArgs; } default: throw new Exception($"Invalid arguments type {argumentsType}"); diff --git a/OpenDreamRuntime/Procs/DebugAdapter/DreamDebugManager.cs b/OpenDreamRuntime/Procs/DebugAdapter/DreamDebugManager.cs index 6b8f7c8c47..fd6ce72c43 100644 --- a/OpenDreamRuntime/Procs/DebugAdapter/DreamDebugManager.cs +++ b/OpenDreamRuntime/Procs/DebugAdapter/DreamDebugManager.cs @@ -763,7 +763,9 @@ private IEnumerable ExpandObject(RequestVariables req, DreamObject obj Variable described; try { - described = DescribeValue(name, obj.GetVariable(name)); + using var value = obj.GetVariable(name); + + described = DescribeValue(name, value); } catch (Exception ex) { _sawmill.Log(LogLevel.Error, ex, $"Error in GetVariable({name})"); diff --git a/OpenDreamRuntime/Procs/DreamEnumerators.cs b/OpenDreamRuntime/Procs/DreamEnumerators.cs index 901e2ef35b..21323504c8 100644 --- a/OpenDreamRuntime/Procs/DreamEnumerators.cs +++ b/OpenDreamRuntime/Procs/DreamEnumerators.cs @@ -3,7 +3,7 @@ namespace OpenDreamRuntime.Procs; -public interface IDreamValueEnumerator { +public interface IDreamValueEnumerator : IDisposable { /// /// Perform the next enumeration, used to advance the state of many types of loops. /// @@ -32,6 +32,8 @@ public bool Enumerate(DMProcState state, DreamReference reference, DreamReferenc return successful; } + + public void Dispose() { } } /// @@ -57,6 +59,10 @@ public bool Enumerate(DMProcState state, DreamReference reference, DreamReferenc state.AssignReference(assocReference, DreamValue.Null); return success; } + + public void Dispose() { + _dreamObjectEnumerator.Dispose(); + } } /// @@ -78,6 +84,14 @@ public bool Enumerate(DMProcState state, DreamReference reference, DreamReferenc state.AssignReference(assocReference, assocValues?.GetValueOrDefault(value, DreamValue.Null) ?? DreamValue.Null); return success; } + + public void Dispose() { + foreach (var value in values) + value.Dispose(); + if (assocValues != null) + foreach (var assocValue in assocValues.Values) + assocValue.Dispose(); + } } /// @@ -102,10 +116,19 @@ public bool Enumerate(DMProcState state, DreamReference reference, DreamReferenc state.AssignReference(reference, value); if (assocReference != DreamReference.NoRef) state.AssignReference(assocReference, assocValues?.GetValueOrDefault(value, DreamValue.Null) ?? DreamValue.Null); + value.Dispose(); return true; + } else { + value.Dispose(); } } while (true); } + + public void Dispose() { + if (assocValues != null) + foreach (var assocValue in assocValues.Values) + assocValue.Dispose(); + } } /// @@ -123,4 +146,8 @@ public bool Enumerate(DMProcState state, DreamReference reference, DreamReferenc state.AssignReference(assocReference, DreamValue.Null); return success; } + + public void Dispose() { + _enumerator.Dispose(); + } } diff --git a/OpenDreamRuntime/Procs/DreamProcArguments.cs b/OpenDreamRuntime/Procs/DreamProcArguments.cs index 70e27366f7..88cf16d578 100644 --- a/OpenDreamRuntime/Procs/DreamProcArguments.cs +++ b/OpenDreamRuntime/Procs/DreamProcArguments.cs @@ -1,9 +1,11 @@ using System.Runtime.CompilerServices; using System.Text; +using JetBrains.Annotations; namespace OpenDreamRuntime.Procs; -public readonly ref struct DreamProcArguments { +[MustDisposeResource] +public readonly ref struct DreamProcArguments : IDisposable { public int Count => Values.Length; public readonly ReadOnlySpan Values; @@ -14,10 +16,14 @@ public DreamProcArguments() { public DreamProcArguments(ReadOnlySpan values) { Values = values; + foreach (var value in Values) + value.IncRef(); } public DreamProcArguments(params DreamValue[] values) { Values = values; + foreach (var value in Values) + value.IncRef(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -44,4 +50,9 @@ public override string ToString() { strBuilder.Append(')'); return strBuilder.ToString(); } + + public void Dispose() { + foreach (var value in Values) + value.Dispose(); + } } diff --git a/OpenDreamRuntime/Procs/InitDreamObject.cs b/OpenDreamRuntime/Procs/InitDreamObject.cs index 310485a1ab..1b13deba1b 100644 --- a/OpenDreamRuntime/Procs/InitDreamObject.cs +++ b/OpenDreamRuntime/Procs/InitDreamObject.cs @@ -1,123 +1,130 @@ using System.Text; +using JetBrains.Annotations; using OpenDreamRuntime.Objects; -namespace OpenDreamRuntime.Procs { - internal sealed class InitDreamObjectState : ProcState { - public static readonly Stack Pool = new(); +namespace OpenDreamRuntime.Procs; - private readonly DreamManager _dreamMan; - private readonly DreamObjectTree _objectTree; +internal sealed class InitDreamObjectState(DreamManager dreamManager, DreamObjectTree objectTree) : ProcState { + public static readonly Stack Pool = new(); - private enum Stage { - // Need to call the object's (init) proc - Init, + private enum Stage { + // Need to call the object's (init) proc + Init, - // Need to call IDreamMetaObject.OnObjectCreated & New - OnObjectCreated, + // Need to call IDreamMetaObject.OnObjectCreated & New + OnObjectCreated, - // Time to return - Return, - } - - public InitDreamObjectState(DreamManager dreamManager, DreamObjectTree objectTree) { - _dreamMan = dreamManager; - _objectTree = objectTree; - } + // Time to return + Return, + } - public void Initialize(DreamThread thread, DreamObject dreamObject, DreamObject? usr, DreamProcArguments arguments) { - base.Initialize(thread, true); + public void Initialize(DreamThread thread, DreamObject dreamObject, DreamObject? usr, [HandlesResourceDisposal] DreamProcArguments arguments) { + base.Initialize(thread, true); - _dreamObject = dreamObject; - _usr = usr; - arguments.Values.CopyTo(_arguments); - _argumentCount = arguments.Count; - _stage = Stage.Init; - } + _dreamObject = dreamObject; + _dreamObject.IncRef(); + _usr = usr; + _usr?.IncRef(); + arguments.Values.CopyTo(_arguments); + _argumentCount = arguments.Count; + _stage = Stage.Init; + } - private DreamObject _dreamObject; - private DreamObject? _usr; - private readonly DreamValue[] _arguments = new DreamValue[256]; - private int _argumentCount; - private Stage _stage = Stage.Init; + private DreamObject _dreamObject; + private DreamObject? _usr; + private readonly DreamValue[] _arguments = new DreamValue[256]; + private int _argumentCount; + private Stage _stage = Stage.Init; - public override DreamProc? Proc => null; + public override DreamProc? Proc => null; - #if TOOLS +#if TOOLS public override (string SourceFile, int Line) TracyLocationId => ($"new {_dreamObject.ObjectDefinition.Type}",0); - #endif +#endif - public override void AppendStackFrame(StringBuilder builder) { - builder.AppendLine($"new {_dreamObject.ObjectDefinition.Type}"); - } - - public override ReadOnlySpan GetArguments() { - return _arguments.AsSpan(0, ArgumentCount); - } + public override void AppendStackFrame(StringBuilder builder) { + builder.AppendLine($"new {_dreamObject.ObjectDefinition.Type}"); + } - public override void SetArgument(int id, DreamValue value) { - if (id < 0 || id >= ArgumentCount) - throw new IndexOutOfRangeException($"Given argument id ({id}) was out of range"); + public override ReadOnlySpan GetArguments() { + return _arguments.AsSpan(0, ArgumentCount); + } - _arguments[id] = value; - } + public override void SetArgument(int id, DreamValue value) { + if (id < 0 || id >= ArgumentCount) + throw new IndexOutOfRangeException($"Given argument id ({id}) was out of range"); - public override void Dispose() { - base.Dispose(); + value.IncRef(); + _arguments[id].DecRef(); + _arguments[id] = value; + } - _dreamObject = null!; - _usr = null; - _argumentCount = 0; + public override void Dispose() { + base.Dispose(); - Pool.Push(this); + for (int i = 0; i < _argumentCount; i++) { + _arguments[i].Dispose(); } - public override ProcStatus Resume() { - var src = _dreamObject; + Array.Clear(_arguments, 0, _argumentCount); + _dreamObject.DecRef(); + _dreamObject = null!; + _usr?.DecRef(); + _usr = null; + _argumentCount = 0; - switch_start: - switch (_stage) { - case Stage.Init: { - _stage = Stage.OnObjectCreated; + Pool.Push(this); + } - if (src.ObjectDefinition.InitializationProc == null || _objectTree.Procs[src.ObjectDefinition.InitializationProc.Value] is DMProc { IsNullProc: true }) { - goto switch_start; - } + public override ProcStatus Resume() { + var src = _dreamObject; - var proc = _objectTree.Procs[src.ObjectDefinition.InitializationProc.Value]; - var initProcState = proc.CreateState(Thread, src, _usr, new()); - Thread.PushProcState(initProcState); - return ProcStatus.Called; + switch_start: + switch (_stage) { + case Stage.Init: { + _stage = Stage.OnObjectCreated; + + if (src.ObjectDefinition.InitializationProc == null || objectTree.Procs[src.ObjectDefinition.InitializationProc.Value] is DMProc { IsNullProc: true }) { + goto switch_start; } - case Stage.OnObjectCreated: { - _stage = Stage.Return; + var proc = objectTree.Procs[src.ObjectDefinition.InitializationProc.Value]; + var initProcState = proc.CreateState(Thread, src, _usr, new()); + Thread.PushProcState(initProcState); + return ProcStatus.Called; + } - _dreamObject.Initialize(new DreamProcArguments(_arguments.AsSpan(0, _argumentCount))); + case Stage.OnObjectCreated: { + _stage = Stage.Return; - if (!_dreamMan.Initialized) { - // Suppress all New() calls during /world/() and map loading. - goto switch_start; - } + using var initArgs = new DreamProcArguments(_arguments.AsSpan(0, _argumentCount)); + _dreamObject.Initialize(initArgs); - if (src.ShouldCallNew) { - var newProc = src.GetProc("New"); - if (newProc is DMProc { IsNullProc: true}) - goto switch_start; + if (!dreamManager.Initialized) { + // Suppress all New() calls during /world/() and map loading. + goto switch_start; + } - var newProcState = newProc.CreateState(Thread, src, _usr, new DreamProcArguments(_arguments.AsSpan(0, _argumentCount))); - Thread.PushProcState(newProcState); - return ProcStatus.Called; - } + if (src.ShouldCallNew) { + var newProc = src.GetProc("New"); + if (newProc is DMProc { IsNullProc: true }) + goto switch_start; - goto switch_start; + var args = _arguments.AsSpan(0, _argumentCount); + var newProcState = newProc.CreateState(Thread, src, _usr, new DreamProcArguments(args)); + Thread.PushProcState(newProcState); + return ProcStatus.Called; } - case Stage.Return: - Result = new DreamValue(_dreamObject); - return ProcStatus.Returned; + goto switch_start; } - throw new InvalidOperationException(); + case Stage.Return: + Result = new DreamValue(_dreamObject); + Result.IncRef(); + return ProcStatus.Returned; } + + throw new InvalidOperationException(); } } diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs index 34a227b15e..2cd97d84e2 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs @@ -83,6 +83,7 @@ public static void SetupNativeProcs(DreamObjectTree objectTree) { objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_rand_seed); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_range); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_ref); + objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_refcount); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_regex); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_replacetext); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_replacetext_char); diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeList.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeList.cs index 24a4f3d919..7d33dde22c 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeList.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeList.cs @@ -30,6 +30,7 @@ public static DreamValue NativeProc_Copy(NativeProc.Bundle bundle, DreamObject? int start = bundle.GetArgument(0, "Start").MustGetValueAsInteger(); //1-indexed int end = bundle.GetArgument(1, "End").MustGetValueAsInteger(); //1-indexed IDreamList list = (IDreamList)src!; + if (list is DreamList) { DreamList listCopy = (DreamList)list.CreateCopy(start, end); return new DreamValue(listCopy); diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeMatrix.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeMatrix.cs index aaecbbea1f..10d26d07b9 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeMatrix.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeMatrix.cs @@ -3,50 +3,58 @@ using DreamValueTypeFlag = OpenDreamRuntime.DreamValue.DreamValueTypeFlag; namespace OpenDreamRuntime.Procs.Native; -internal static class DreamProcNativeMatrix { +internal static class DreamProcNativeMatrix { [DreamProc("Add")] [DreamProcParameter("Matrix2", Type = DreamValueTypeFlag.DreamObject)] public static DreamValue NativeProc_Add(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { DreamValue possibleMatrix = bundle.GetArgument(0, "Matrix2"); if (possibleMatrix.TryGetValueAsDreamObject(out var matrixArg)) { DreamObjectMatrix.AddMatrix((DreamObjectMatrix)src!, matrixArg); - return new DreamValue(src!); + + src!.IncRef(); + return new DreamValue(src); } + // On invalid input, throw runtime throw new Exception($"Invalid matrix for addition: {possibleMatrix.ToString()}"); } - [DreamProc("Invert")] public static DreamValue NativeProc_Invert(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { if (!DreamObjectMatrix.TryInvert((DreamObjectMatrix)src!)) { throw new ArgumentException("Matrix does not have a valid inversion for Invert()"); } - return new DreamValue(src!); + src!.IncRef(); + return new DreamValue(src); } [DreamProc("Multiply")] [DreamProcParameter("Matrix2", Type = DreamValueTypeFlag.DreamObject | DreamValueTypeFlag.Float)] // or "n" public static DreamValue NativeProc_Multiply(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { + src!.IncRef(); + DreamValue possibleMatrix = bundle.GetArgument(0, "Matrix2"); if (possibleMatrix.TryGetValueAsDreamObject(out var matrixArg)) { - DreamObjectMatrix.MultiplyMatrix((DreamObjectMatrix)src!, matrixArg); - return new DreamValue(src!); + DreamObjectMatrix.MultiplyMatrix((DreamObjectMatrix)src, matrixArg); + return new DreamValue(src); } + // The other valid call is with a number "n" if (possibleMatrix.TryGetValueAsFloat(out float n)) { - DreamObjectMatrix.ScaleMatrix((DreamObjectMatrix)src!, n, n); - return new DreamValue(src!); + DreamObjectMatrix.ScaleMatrix((DreamObjectMatrix)src, n, n); + return new DreamValue(src); } + // Special case: If null was passed, return src if (possibleMatrix.Equals(DreamValue.Null)) { - return new DreamValue(src!); + return new DreamValue(src); } + // Give up and turn the input into the zero matrix on invalid input - DreamObjectMatrix.ScaleMatrix((DreamObjectMatrix)src!, 0, 0); - return new DreamValue(src!); + DreamObjectMatrix.ScaleMatrix((DreamObjectMatrix)src, 0, 0); + return new DreamValue(src); } [DreamProc("Scale")] @@ -58,7 +66,8 @@ public static DreamValue NativeProc_Scale(NativeProc.Bundle bundle, DreamObject? verticalScale = horizontalScale; DreamObjectMatrix.ScaleMatrix((DreamObjectMatrix)src!, horizontalScale, verticalScale); - return new DreamValue(src!); + src!.IncRef(); + return new DreamValue(src); } [DreamProc("Subtract")] @@ -67,8 +76,11 @@ public static DreamValue NativeProc_Subtract(NativeProc.Bundle bundle, DreamObje DreamValue possibleMatrix = bundle.GetArgument(0, "Matrix2"); if (possibleMatrix.TryGetValueAsDreamObject(out var matrixArg)) { DreamObjectMatrix.SubtractMatrix((DreamObjectMatrix)src!, matrixArg); - return new DreamValue(src!); + + src!.IncRef(); + return new DreamValue(src); } + // On invalid input, throw runtime throw new Exception($"Invalid matrix for subtraction: {possibleMatrix.ToString()}"); } @@ -78,10 +90,8 @@ public static DreamValue NativeProc_Subtract(NativeProc.Bundle bundle, DreamObje [DreamProcParameter("x", Type = DreamValueTypeFlag.Float)] [DreamProcParameter("y", Type = DreamValueTypeFlag.Float)] public static DreamValue NativeProc_Translate(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - DreamValue xArgument = bundle.GetArgument(0, "x"); - if (xArgument.Equals(DreamValue.Null) || !xArgument.TryGetValueAsFloat(out float xTranslation)) { - xTranslation = 0; // Defaults to 0 on an invalid value or a passed null - } + var xArgument = bundle.GetArgument(0, "x"); + var xTranslation = xArgument.UnsafeGetValueAsFloat(); // Defaults to 0 on an invalid value or a passed null float yTranslation; // If y is null or not provided, use the value of x. If it is otherwise invalid, treat it as 0. @@ -92,14 +102,13 @@ public static DreamValue NativeProc_Translate(NativeProc.Bundle bundle, DreamObj yTranslation = 0; } - // Avoid translating - if (xTranslation == 0 && yTranslation == 0) { - return new DreamValue(src!); + // Avoid translating if unnecessary + if (xTranslation != 0 || yTranslation != 0) { + DreamObjectMatrix.TranslateMatrix((DreamObjectMatrix)src!, xTranslation, yTranslation); } - DreamObjectMatrix.TranslateMatrix((DreamObjectMatrix)src!, xTranslation, yTranslation); - - return new DreamValue(src!); + src!.IncRef(); + return new DreamValue(src); } [DreamProc("Turn")] @@ -109,7 +118,9 @@ public static DreamValue NativeProc_Turn(NativeProc.Bundle bundle, DreamObject? if (!angleArg.TryGetValueAsFloat(out float angle)) { return new DreamValue(src!); // Defaults to input on invalid angle } - return _NativeProc_TurnInternal(bundle.ObjectTree, (DreamObjectMatrix)src!, angle); + + src!.IncRef(); + return _NativeProc_TurnInternal(bundle.ObjectTree, (DreamObjectMatrix)src, angle); } /// Turns a given matrix a given amount of degrees clockwise. @@ -123,6 +134,7 @@ public static DreamValue _NativeProc_TurnInternal(DreamObjectTree objectTree, Dr var rotationMatrix = DreamObjectMatrix.MakeMatrix(objectTree ,angleCos, angleSin, 0, -angleSin, angleCos, 0); DreamObjectMatrix.MultiplyMatrix(src, rotationMatrix); + rotationMatrix.DecRef(); return new DreamValue(src); } diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRegex.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRegex.cs index 87713618fe..bc34a94c45 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRegex.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRegex.cs @@ -70,13 +70,13 @@ DreamValue DoProcReplace(DreamProc proc) { // TODO: src is the regex string // TODO: We need to add this to our current thread instead of spawning a new one // TODO: This call needs to immediately die upon sleeping - var result = proc.Spawn(null, new(args)); + using var result = proc.Spawn(null, new(args)); var replacement = result.Stringify(); currentHaystack = regex.Regex.Replace(currentHaystack, replacement, 1, currentStart); currentStart = match.Index + Math.Max(replacement.Length, 1); - if (!regex.IsGlobal){ + if (!regex.IsGlobal) { regex.SetVariable("next", new DreamValue(currentStart + 1)); break; } @@ -122,8 +122,9 @@ public static DreamValue NativeProc_Replace(NativeProc.Bundle bundle, DreamObjec private static int GetNext(DreamObject regexInstance, DreamValue startParam, bool isGlobal, string haystackString) { if (startParam.IsNull) { - if (isGlobal && regexInstance.GetVariable("text").TryGetValueAsString(out string? lastHaystack) && lastHaystack == haystackString) { - DreamValue nextVar = regexInstance.GetVariable("next"); + using var textVar = regexInstance.GetVariable("text"); + if (isGlobal && textVar.TryGetValueAsString(out string? lastHaystack) && lastHaystack == haystackString) { + using var nextVar = regexInstance.GetVariable("next"); return (!nextVar.IsNull) ? nextVar.GetValueAsInteger() : 1; } else { diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs index ef8032bb9b..f660270661 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs @@ -17,6 +17,7 @@ using System.Threading.Tasks; using System.Web; using DMCompiler.DM; +using JetBrains.Annotations; using OpenDreamRuntime.Map; using OpenDreamRuntime.Objects.Types; using OpenDreamRuntime.Rendering; @@ -777,7 +778,8 @@ public static DreamValue NativeProc_flist(NativeProc.Bundle bundle, DreamObject? try { var listing = bundle.ResourceManager.EnumerateListing(path); - DreamList list = bundle.ObjectTree.CreateList(listing); + var list = bundle.ObjectTree.CreateList(listing); + return new DreamValue(list); } catch (DirectoryNotFoundException) { return new DreamValue(bundle.ObjectTree.CreateList()); // empty list @@ -878,7 +880,12 @@ public static DreamValue NativeProc_get_steps_to(NativeProc.Bundle bundle, Dream } // Null if there are no steps - return new(result.GetLength() > 0 ? result : null); + if (result.GetLength() == 0) { + result.DecRef(); + return DreamValue.Null; + } else { + return new(result); + } } [DreamProc("generator")] @@ -888,6 +895,7 @@ public static DreamValue NativeProc_get_steps_to(NativeProc.Bundle bundle, Dream [DreamProcParameter("rand", Type = DreamValueTypeFlag.Float, DefaultValue = 0)] public static DreamValue NativeProc_generator(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { DreamObject generator = bundle.ObjectTree.CreateObject(bundle.ObjectTree.Generator); + generator.InitSpawn(new DreamProcArguments(bundle.Arguments)); return new DreamValue(generator); } @@ -961,6 +969,7 @@ public static DreamValue NativeProc_icon_states(NativeProc.Bundle bundle, DreamO [DreamProcParameter("pixel_y", Type = DreamValueTypeFlag.Float)] public static DreamValue NativeProc_image(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { DreamObject imageObject = bundle.ObjectTree.CreateObject(bundle.ObjectTree.Image); + imageObject.InitSpawn(new DreamProcArguments(bundle.Arguments)); // TODO: Don't create another thread return new DreamValue(imageObject); } @@ -1132,13 +1141,14 @@ public static DreamValue NativeProc_isturf(NativeProc.Bundle bundle, DreamObject return DreamValue.True; } + [MustDisposeResource] private static DreamValue CreateValueFromJsonElement(DreamObjectTree objectTree, JsonElement jsonElement) { switch (jsonElement.ValueKind) { case JsonValueKind.Array: { DreamList list = objectTree.CreateList(jsonElement.GetArrayLength()); foreach (JsonElement childElement in jsonElement.EnumerateArray()) { - DreamValue value = CreateValueFromJsonElement(objectTree, childElement); + using var value = CreateValueFromJsonElement(objectTree, childElement); list.AddValue(value); } @@ -1172,19 +1182,19 @@ private static DreamValue CreateValueFromJsonElement(DreamObjectTree objectTree, // It was not a single-property? Or the property was not special? // FANTASTIC. STOP PRETENDING BEING A PARSER AND INSERT THEM IN A LIST - DreamValue v1 = CreateValueFromJsonElement(objectTree, first.Value); + using var v1 = CreateValueFromJsonElement(objectTree, first.Value); list.SetValue(new DreamValue(first.Name), v1); if(!hasSecond) return new DreamValue(list); var second = enumerator.Current; - DreamValue v2 = CreateValueFromJsonElement(objectTree, second.Value); + using var v2 = CreateValueFromJsonElement(objectTree, second.Value); list.SetValue(new DreamValue(second.Name), v2); // Enumerate the damn rest of the godawful fucking shitty JSON foreach (JsonProperty childProperty in jsonElement.EnumerateObject()) { - DreamValue value = CreateValueFromJsonElement(objectTree, childProperty.Value); + using var value = CreateValueFromJsonElement(objectTree, childProperty.Value); list.SetValue(new DreamValue(childProperty.Name), value); } @@ -1246,9 +1256,10 @@ private static void JsonEncode(Utf8JsonWriter writer, DreamValue value) { var key = listValue.Stringify(); if (list.ContainsKey(listValue)) { - var subValue = list.GetValue(listValue); - if(subValue.TryGetValueAsDreamList(out var subList) && subList is DreamListVars) //BYOND parity, do not print vars["vars"] - note that this is *not* a generic infinite loop protection on purpose + using var subValue = list.GetValue(listValue); + if (subValue.TryGetValueAsDreamList(out var subList) && subList is DreamListVars) //BYOND parity, do not print vars["vars"] - note that this is *not* a generic infinite loop protection on purpose continue; + writer.WritePropertyName(key); JsonEncode(writer, subValue); } else { @@ -1284,10 +1295,7 @@ private static void JsonEncode(Utf8JsonWriter writer, DreamValue value) { break; } case DreamObjectVector vector: { // Special behaviour for /vector values - if (vector.Is3D) - writer.WriteStringValue($"vector({vector.X},{vector.Y},{vector.Z})"); - else - writer.WriteStringValue($"vector({vector.X},{vector.Y})"); + writer.WriteStringValue(vector.ToString()); break; } default: @@ -1303,6 +1311,7 @@ private static void JsonEncode(Utf8JsonWriter writer, DreamValue value) { [DreamProc("json_decode")] [DreamProcParameter("JSON", Type = DreamValueTypeFlag.String)] + [MustDisposeResource] public static DreamValue NativeProc_json_decode(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { if (!bundle.GetArgument(0, "JSON").TryGetValueAsString(out var jsonString)) { throw new Exception("Unknown value"); @@ -1411,6 +1420,7 @@ public static DreamValue NativeProc_matrix(NativeProc.Bundle bundle, DreamObject case 6: // Take the arguments and construct a matrix. case 0: // Since arguments are empty, this just creates an identity matrix. matrix = bundle.ObjectTree.CreateObject(bundle.ObjectTree.Matrix); + matrix.InitSpawn(new DreamProcArguments(bundle.Arguments)); return new DreamValue(matrix); case 1: // Clone the matrix. @@ -1467,12 +1477,14 @@ public static DreamValue NativeProc_matrix(NativeProc.Bundle bundle, DreamObject case MatrixOpcode.Invert: if (!firstArgument.TryGetValueAsDreamObject(out var matrixInput)) // Expecting a matrix here throw new ArgumentException($"/matrix() called with invalid argument '{firstArgument}'"); + //Choose whether we are inverting the original matrix or a clone of it var invertableMatrix = doModify ? matrixInput : DreamObjectMatrix.MatrixClone(bundle.ObjectTree, matrixInput); - if (!DreamObjectMatrix.TryInvert(invertableMatrix)) - throw new ArgumentException("/matrix provided for MATRIX_INVERT cannot be inverted"); + if (DreamObjectMatrix.TryInvert(invertableMatrix)) + return new DreamValue(invertableMatrix); - return new DreamValue(invertableMatrix); + invertableMatrix.DecRef(); + throw new ArgumentException("/matrix provided for MATRIX_INVERT cannot be inverted"); case MatrixOpcode.Rotate: var angleArgument = firstArgument; if (firstArgument.TryGetValueAsDreamObject(out var matrixToRotate)) @@ -1506,13 +1518,24 @@ public static DreamValue NativeProc_matrix(NativeProc.Bundle bundle, DreamObject float horizontalScale; float verticalScale; if (firstArgument.TryGetValueAsDreamObject(out var matrixArgument)) { // scaling a matrix - var scaledMatrix = doModify ? matrixArgument : DreamObjectMatrix.MatrixClone(bundle.ObjectTree, matrixArgument); + var scaledMatrix = doModify + ? matrixArgument + : DreamObjectMatrix.MatrixClone(bundle.ObjectTree, matrixArgument); + + if (!secondArgument.TryGetValueAsFloat(out horizontalScale)) { + if (!doModify) + scaledMatrix.DecRef(); - if (!secondArgument.TryGetValueAsFloat(out horizontalScale)) throw new ArgumentException($"/matrix() called with invalid scaling factor '{secondArgument}'"); + } + if (bundle.Arguments.Length == 4) { - if (!bundle.GetArgument(2, "c").TryGetValueAsFloat(out verticalScale)) + if (!bundle.GetArgument(2, "c").TryGetValueAsFloat(out verticalScale)) { + if (!doModify) + scaledMatrix.DecRef(); + throw new ArgumentException($"/matrix() called with invalid scaling factor '{bundle.GetArgument(2, "c")}'"); + } } else { verticalScale = horizontalScale; } @@ -1548,18 +1571,15 @@ public static DreamValue NativeProc_matrix(NativeProc.Bundle bundle, DreamObject translateMatrix = DreamObjectMatrix.MatrixClone(bundle.ObjectTree, targetMatrix); bundle.GetArgument(1,"b").TryGetValueAsFloat(out float horizontalOffset); - translateMatrix.GetVariable("c").TryGetValueAsFloat(out float oldXOffset); - translateMatrix.SetVariableValue("c", new(horizontalOffset + oldXOffset)); + translateMatrix.C += horizontalOffset; bundle.GetArgument(2, "c").TryGetValueAsFloat(out float verticalOffset); - translateMatrix.GetVariable("f").TryGetValueAsFloat(out float oldYOffset); - translateMatrix.SetVariableValue("f", new(verticalOffset + oldYOffset)); + translateMatrix.F += verticalOffset; return new DreamValue(translateMatrix); } - float horizontalShift; float verticalShift; - if (!firstArgument.TryGetValueAsFloat(out horizontalShift)) + if (!firstArgument.TryGetValueAsFloat(out var horizontalShift)) throw new ArgumentException($"/matrix() called with invalid translation factor '{firstArgument}'"); if (bundle.Arguments.Length == 3) { var secondArg = bundle.GetArgument(1, "b"); @@ -1845,8 +1865,10 @@ private static string List2Params(IDreamList list) { foreach (DreamValue entry in list.EnumerateValues()) { if (list.ContainsKey(entry)) { + using var entryValue = list.GetValue(entry); + paramBuilder.Append( - $"{HttpUtility.UrlEncode(entry.Stringify())}={HttpUtility.UrlEncode(list.GetValue(entry).Stringify())}"); + $"{HttpUtility.UrlEncode(entry.Stringify())}={HttpUtility.UrlEncode(entryValue.Stringify())}"); } else { paramBuilder.Append(HttpUtility.UrlEncode(entry.Stringify())); } @@ -1881,6 +1903,7 @@ public static DreamList Params2List(DreamObjectTree objectTree, string queryStri valueList.AddValue(new(string.Empty)); list.SetValue(new(value), new(valueList)); + valueList.DecRef(); } else { list.SetValue(new(value), new(string.Empty)); } @@ -1945,24 +1968,34 @@ public static DreamValue NativeProc_range(NativeProc.Bundle bundle, DreamObject? (DreamObjectAtom? center, ViewRange range) = DreamProcNativeHelpers.ResolveViewArguments(bundle.DreamManager, usr as DreamObjectAtom, bundle.Arguments); if (center is null) return new DreamValue(bundle.ObjectTree.CreateList()); + DreamList rangeList = bundle.ObjectTree.CreateList(range.Height * range.Width); + //Have to include centre rangeList.AddValue(new DreamValue(center)); + if(center.TryGetVariable("contents", out var centerContents) && centerContents.TryGetValueAsDreamList(out var centerContentsList)) { foreach(DreamValue content in centerContentsList.EnumerateValues()) { rangeList.AddValue(content); } } - if (center is not DreamObjectTurf) { // If it's not a /turf, we have to include its loc and the loc's contents - if(center.TryGetVariable("loc",out DreamValue centerLoc) && centerLoc.TryGetValueAsDreamObject(out var centerLocObject)) { + centerContents.Dispose(); + + // If it's not a /turf, we have to include its loc and the loc's contents + if (center is not DreamObjectTurf && center.TryGetVariable("loc",out DreamValue centerLoc)) { + if (centerLoc.TryGetValueAsDreamObject(out var centerLocObject)) { rangeList.AddValue(centerLoc); - if(centerLocObject.GetVariable("contents").TryGetValueAsDreamList(out var locContentsList)) { + + using var contents = centerLocObject.GetVariable("contents"); + if (contents.TryGetValueAsDreamList(out var locContentsList)) { foreach (DreamValue content in locContentsList.EnumerateValues()) { rangeList.AddValue(content); } } } + + centerLoc.Dispose(); } //And then everything else @@ -1985,6 +2018,16 @@ public static DreamValue NativeProc_ref(NativeProc.Bundle bundle, DreamObject? s return new DreamValue(dreamRef); } + [DreamProc("refcount")] + [DreamProcParameter("Object", Type = DreamValueTypeFlag.DreamObject)] + public static DreamValue NativeProc_refcount(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { + var value = bundle.GetArgument(0, "Object"); + if (!value.TryGetValueAsDreamObject(out var dreamObject)) + return new(0); + + return new(dreamObject.RefCount - 1); // Don't count the active reference that refcount() is holding + } + [DreamProc("regex")] [DreamProcParameter("pattern", Type = DreamValueTypeFlag.String | DreamValueTypeFlag.DreamObject)] [DreamProcParameter("flags", Type = DreamValueTypeFlag.Float)] @@ -2001,6 +2044,7 @@ public static DreamValue NativeProc_regex(NativeProc.Bundle bundle, DreamObject? } var newRegex = bundle.ObjectTree.CreateObject(bundle.ObjectTree.Regex); + newRegex.InitSpawn(new DreamProcArguments(bundle.Arguments)); return new DreamValue(newRegex); } @@ -2199,7 +2243,7 @@ public static DreamValue NativeProc_rgb2num(NativeProc.Bundle bundle, DreamObjec break; } - if (color.Length == 9 || color.Length == 5) { + if (color.Length is 9 or 5) { list.AddValue(new DreamValue(c.AByte)); } @@ -2472,6 +2516,7 @@ public static DreamValue NativeProc_spantext_char(NativeProc.Bundle bundle, Drea [DreamProcParameter("volume", Type = DreamValueTypeFlag.Float)] public static DreamValue NativeProc_sound(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { DreamObject soundObject = bundle.ObjectTree.CreateObject(bundle.ObjectTree.Sound); + soundObject.InitSpawn(new DreamProcArguments(bundle.Arguments)); return new DreamValue(soundObject); } @@ -2825,7 +2870,7 @@ public static DreamValue NativeProc_time2text(NativeProc.Bundle bundle, DreamObj if (!bundle.GetArgument(0, "timestamp").TryGetValueAsFloat(out var timestamp)) { // TODO This copes with nulls and is a sane default, but BYOND has weird returns for strings and stuff - bundle.DreamManager.WorldInstance.GetVariable("timeofday").TryGetValueAsFloat(out timestamp); + timestamp = bundle.DreamManager.WorldInstance.TimeOfDay; } if (!bundle.GetArgument(1, "format").TryGetValueAsString(out var format)) { @@ -3212,6 +3257,8 @@ public static DreamValue NativeProc_view(NativeProc.Bundle bundle, DreamObject? } } + centerContents.Dispose(); + // Center gets included during the walk through the tiles var eyePos = bundle.AtomManager.GetAtomPosition(center); diff --git a/OpenDreamRuntime/Procs/NativeProc.cs b/OpenDreamRuntime/Procs/NativeProc.cs index 6ddd8e43ef..59ade6c8ed 100644 --- a/OpenDreamRuntime/Procs/NativeProc.cs +++ b/OpenDreamRuntime/Procs/NativeProc.cs @@ -1,7 +1,7 @@ using System.Reflection; -using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using DMCompiler.DM; +using JetBrains.Annotations; using OpenDreamRuntime.Objects; using OpenDreamRuntime.Resources; using OpenDreamRuntime.Map; @@ -62,7 +62,7 @@ public readonly ref struct Bundle(NativeProc proc, DreamProcArguments arguments) public WalkManager WalkManager => Proc._walkManager; public DreamObjectTree ObjectTree => Proc._objectTree; - [Pure] + [System.Diagnostics.Contracts.Pure] public DreamValue GetArgument(int argumentPosition, string argumentName) { if (Arguments.Length > argumentPosition && !Arguments[argumentPosition].IsNull) { return Arguments[argumentPosition]; @@ -99,10 +99,12 @@ public override ProcState CreateState(DreamThread thread, DreamObject? src, Drea throw new InvalidOperationException("Synchronous native procs cannot create a state. Use Call() instead."); } - public DreamValue Call(DreamThread thread, DreamObject? src, DreamObject? usr, DreamProcArguments arguments) { + [MustDisposeResource] + public DreamValue Call(DreamThread thread, DreamObject? src, DreamObject? usr, [HandlesResourceDisposal] DreamProcArguments arguments) { var bundle = new Bundle(this, arguments); + var result = _handler(bundle, src, usr); // TODO: Include this call in the thread's stack in error traces - // TODO: Include this call in the thread's stack in error traces - return _handler(bundle, src, usr); + arguments.Dispose(); + return result; } } diff --git a/OpenDreamRuntime/Procs/ProcScheduler.cs b/OpenDreamRuntime/Procs/ProcScheduler.cs index ae890c3c36..36a970c1e4 100644 --- a/OpenDreamRuntime/Procs/ProcScheduler.cs +++ b/OpenDreamRuntime/Procs/ProcScheduler.cs @@ -69,11 +69,13 @@ public IEnumerable InspectThreads() { if (_current?.Thread is not null) { yield return _current.Thread; } + foreach (var state in _scheduled) { if (state.Thread == null) continue; yield return state.Thread; } + foreach (var state in _sleeping) { if (state.Thread == null) continue; diff --git a/OpenDreamRuntime/ServerVerbSystem.cs b/OpenDreamRuntime/ServerVerbSystem.cs index dd1ddad3d8..8df5e40aed 100644 --- a/OpenDreamRuntime/ServerVerbSystem.cs +++ b/OpenDreamRuntime/ServerVerbSystem.cs @@ -185,7 +185,7 @@ private void RunVerb(DreamProc verb, string name, DreamObject? src, DreamConnect DreamThread.Run($"Execute {name} by {usr.Session!.Name}", async state => { await state.Call(verb, src, usr.Mob, arguments); return DreamValue.Null; - }); + }).Dispose(); } private void OnVerbExecuted(ExecuteVerbEvent msg, EntitySessionEventArgs args) { diff --git a/OpenDreamRuntime/Util/WeakDreamRef.cs b/OpenDreamRuntime/Util/WeakDreamRef.cs deleted file mode 100644 index d5bf82e035..0000000000 --- a/OpenDreamRuntime/Util/WeakDreamRef.cs +++ /dev/null @@ -1,24 +0,0 @@ -using OpenDreamRuntime.Objects; - -namespace OpenDreamRuntime.Util; - -public sealed class WeakDreamRef { - private readonly WeakReference _weakReference; - - public WeakDreamRef(DreamObject obj) => _weakReference = new WeakReference(obj); - - public DreamObject? Target { - get { - _weakReference.TryGetTarget(out var t); - return t; - } - } - - public override bool Equals(object? obj) { - return Target?.Equals(obj) ?? false; - } - - public override int GetHashCode() { - throw new NotSupportedException("WeakDreamRef cannot be used as a hashmap key or similar."); - } -} diff --git a/OpenDreamRuntime/WalkManager.cs b/OpenDreamRuntime/WalkManager.cs index ca372e6adf..aa378ad8a6 100644 --- a/OpenDreamRuntime/WalkManager.cs +++ b/OpenDreamRuntime/WalkManager.cs @@ -1,4 +1,3 @@ -using System.Linq; using System.Threading; using OpenDreamRuntime.Map; using OpenDreamRuntime.Objects.Types; @@ -24,8 +23,10 @@ public sealed class WalkManager { /// Stop any active walks on a movable /// public void StopWalks(DreamObjectMovable movable) { - if (_walkTasks.TryGetValue(movable, out var walk)) + if (_walkTasks.Remove(movable, out var walk)) { walk.Cancel(); + movable.DecRef(); + } } /// @@ -38,6 +39,7 @@ public void StartWalk(DreamObjectMovable movable, int dir, int lag, int speed) { CancellationTokenSource cancelSource = new(); _walkTasks[movable] = cancelSource; + movable.IncRef(); DreamThread.Run($"walk {dir}", async state => { var moveProc = movable.GetProc("Move"); @@ -52,7 +54,7 @@ public void StartWalk(DreamObjectMovable movable, int dir, int lag, int speed) { } return DreamValue.Null; - }); + }).Dispose(); } /// @@ -65,6 +67,7 @@ public void StartWalkRand(DreamObjectMovable movable, int lag, int speed) { // T CancellationTokenSource cancelSource = new(); _walkTasks[movable] = cancelSource; + movable.IncRef(); DreamThread.Run("walk_rand", async state => { var moveProc = movable.GetProc("Move"); @@ -73,13 +76,14 @@ public void StartWalkRand(DreamObjectMovable movable, int lag, int speed) { // T await _scheduler.CreateDelayTicks(lag); if (cancelSource.IsCancellationRequested) break; + var dir = DreamProcNativeHelpers.GetRandomDirection(_dreamManager); DreamObjectTurf? newLoc = DreamProcNativeHelpers.GetStep(_atomManager, _dreamMapManager, movable, dir); await state.Call(moveProc, movable, null, new(newLoc), new((int)dir)); } return DreamValue.Null; - }); + }).Dispose(); } /// @@ -92,6 +96,7 @@ public void StartWalkTowards(DreamObjectMovable movable, DreamObjectAtom target, CancellationTokenSource cancelSource = new(); _walkTasks[movable] = cancelSource; + movable.IncRef(); DreamThread.Run($"walk_towards {movable}", async state => { var moveProc = movable.GetProc("Move"); @@ -110,7 +115,7 @@ public void StartWalkTowards(DreamObjectMovable movable, DreamObjectAtom target, } return DreamValue.Null; - }); + }).Dispose(); } /// @@ -123,6 +128,7 @@ public void StartWalkTo(DreamObjectMovable movable, DreamObjectAtom target, int CancellationTokenSource cancelSource = new(); _walkTasks[movable] = cancelSource; + movable.IncRef(); DreamThread.Run($"walk_to {movable}", async state => { var moveProc = movable.GetProc("Move"); @@ -145,6 +151,6 @@ public void StartWalkTo(DreamObjectMovable movable, DreamObjectAtom target, int } return DreamValue.Null; - }); + }).Dispose(); } }