Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Content.Tests/DMProject/Tests/Builtins/caller.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/datum/meep/proc/bar()
ASSERT(caller.caller.caller.name == "ihateithere")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this not be "RunTest"?

Could also be using nameof(/proc/RunTest) for these asserts instead.


/datum/proc/foo()
var/datum/meep/M = new
ASSERT(caller.name == "ihateithere")
M.bar()

/proc/ihateithere()
var/datum/D = new
ASSERT(caller.name == "RunTest")
D.foo()

/proc/RunTest()
// RunTest()'s caller is null due to how unit tests are invoked
ihateithere()
2 changes: 1 addition & 1 deletion DMCompiler/DMStandard/Types/Callee.dm
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/callee
var/proc
var/args
var/caller as /callee|null
var/callee/caller as /callee|null

var/name as text|null
var/desc as text|null
Expand Down
9 changes: 9 additions & 0 deletions OpenDreamRuntime/DreamThread.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Threading.Tasks;
using DMCompiler.DM;
using OpenDreamRuntime.Objects;
using OpenDreamRuntime.Objects.Types;
using OpenDreamRuntime.Procs;
using OpenDreamRuntime.Procs.DebugAdapter;
using OpenDreamShared.Dream;
Expand Down Expand Up @@ -147,6 +148,9 @@ public abstract class ProcState : IDisposable {
public int ArgumentCount;
public abstract DreamProc? Proc { get; }

protected DreamObjectCallee? Callee { get; set; }
protected DreamObjectCallee? Caller { get; set; }

protected void Initialize(DreamThread thread, bool waitFor) {
Thread = thread;
WaitFor = waitFor;
Expand Down Expand Up @@ -234,6 +238,11 @@ public DreamValue Resume() {
return ReentrantResume(null, out _);
}

public ProcState? PeekStack(int index = 0) {
if (_stack.Count == 0) return null;
return index == 0 ? _stack.Peek() : _stack.ElementAt(index);
}

/// <summary>
/// Resume this thread re-entrantly.
/// </summary>
Expand Down
43 changes: 33 additions & 10 deletions OpenDreamRuntime/Objects/Types/DreamObjectCallee.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,49 @@

namespace OpenDreamRuntime.Objects.Types;

public sealed class DreamObjectCallee(DreamObjectDefinition objectDefinition) : DreamObject(objectDefinition) {
public DMProcState? ProcState;
public sealed class DreamObjectCallee : DreamObject {
public ProcState? ProcState;
public long ProcStateId; // Used to ensure the proc state hasn't been reused for another proc
private DreamObjectCallee? _caller; // Caching caller prevents issues with returning incorrect info when the proc ends or calls another proc

public DreamObjectCallee(DreamObjectDefinition objectDefinition) : base(objectDefinition) {
SetCaller(); // we need to cache _caller recursively
}

protected override bool TryGetVar(string varName, out DreamValue value) {
// TODO: This ProcState check doesn't match byond behavior?
if (ProcState == null || ProcState.Id != ProcStateId)
throw new Exception("This callee has expired");

switch (varName) {
case "proc":
value = new(ProcState.Proc);
value = ProcState.Proc != null ? new(ProcState.Proc) : DreamValue.Null;
return true;
case "args":
value = new(new ProcArgsList(ObjectTree.List.ObjectDefinition, ProcState));
return true;
case "caller":
// TODO
value = DreamValue.Null;
SetCaller(); // sometimes ProcState is null in the constructor?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wouldn't ProcState always be null in the constructor? It's not passed to the constructor or set in there.

value = new DreamValue(_caller);
return true;
case "name":
value = new(ProcState.Proc.VerbName);
value = ProcState.Proc?.VerbName != null ? new(ProcState.Proc.VerbName) : DreamValue.Null;
return true;
case "desc":
value = ProcState.Proc.VerbDesc != null ? new(ProcState.Proc.VerbDesc) : DreamValue.Null;
value = ProcState.Proc?.VerbDesc != null ? new(ProcState.Proc.VerbDesc) : DreamValue.Null;
return true;
case "category":
value = ProcState.Proc.VerbCategory != null ? new(ProcState.Proc.VerbCategory) : DreamValue.Null;
value = ProcState.Proc?.VerbCategory != null ? new(ProcState.Proc.VerbCategory) : DreamValue.Null;
return true;
case "file":
value = new(ProcState.Proc.GetSourceAtOffset(0).Source);
value = ProcState.Proc is DMProc procFile
? new DreamValue(procFile.GetSourceAtOffset(0).Source)
: DreamValue.Null;
return true;
case "line":
value = new(ProcState.Proc.GetSourceAtOffset(0).Line);
value = ProcState.Proc is DMProc procLine
? new DreamValue(procLine.GetSourceAtOffset(0).Line)
: DreamValue.Null;
return true;
case "src":
value = new(ProcState.Instance);
Expand All @@ -54,6 +64,19 @@ protected override bool TryGetVar(string varName, out DreamValue value) {
}
}

/// <summary>
/// Sets <see cref="_caller"/> if it hasn't been already and <see cref="ProcState"/> is not null
/// </summary>
private void SetCaller() {
if (ProcState is null || _caller is not null) return;
if (ProcState.Thread.PeekStack(1) is not DMProcState dmProcState) return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always passing an index of 1 is incorrect


var caller = ObjectTree.CreateObject<DreamObjectCallee>(ObjectTree.Callee);
caller.ProcState = dmProcState;
caller.ProcStateId = dmProcState.Id;
_caller = caller;
}

protected override void SetVar(string varName, DreamValue value) {
throw new Exception($"Cannot set var {varName} on /callee");
}
Expand Down
15 changes: 12 additions & 3 deletions OpenDreamRuntime/Procs/DMProc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -837,16 +837,25 @@ public DreamValue GetReferenceValue(DreamReference reference, bool peek = false)
case DMReference.Type.Args: return new(new ProcArgsList(Proc.ObjectTree.List.ObjectDefinition, this));
case DMReference.Type.World: return new(DreamManager.WorldInstance);
case DMReference.Type.Callee: {
// TODO: BYOND seems to reuse the same object. At least, callee == callee
// BYOND seems to reuse the same object. At least, callee == callee
if (Callee is not null) return new DreamValue(Callee);
var callee = Proc.ObjectTree.CreateObject<DreamObjectCallee>(Proc.ObjectTree.Callee);

callee.ProcState = this;
callee.ProcStateId = Id;
Callee = callee;
return new(callee);
}
case DMReference.Type.Caller: {
// TODO
return DreamValue.Null;
// Note that the ref says that caller still returns a "/callee" object, just with the caller's info
if (Caller is not null) return new DreamValue(Caller);
if (Thread.PeekStack() is not DMProcState dmProcState) return DreamValue.Null;

var caller = Proc.ObjectTree.CreateObject<DreamObjectCallee>(Proc.ObjectTree.Callee);
caller.ProcState = dmProcState;
caller.ProcStateId = dmProcState.Id;
Caller = caller;
return new(caller);
}
case DMReference.Type.Field: {
DreamValue owner = peek ? Peek() : Pop();
Expand Down
Loading