From 5f471dfe99c9fbc9f683e7d5a5b17a4f5cb57984 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 27 Nov 2023 01:24:34 -0500 Subject: [PATCH 1/4] Add support for /world/proc/Tick() Closes #868 --- DMCompiler/DMStandard/Types/World.dm | 4 ++++ OpenDreamRuntime/DreamManager.cs | 14 ++++++++++++-- OpenDreamRuntime/Procs/ProcScheduler.cs | 6 ++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/DMCompiler/DMStandard/Types/World.dm b/DMCompiler/DMStandard/Types/World.dm index d7137e0137..57a22f7e7e 100644 --- a/DMCompiler/DMStandard/Types/World.dm +++ b/DMCompiler/DMStandard/Types/World.dm @@ -113,3 +113,7 @@ proc/PayCredits(player, credits, note) set opendream_unimplemented = TRUE return 0 + + proc/Tick() + set waitfor = FALSE + return null diff --git a/OpenDreamRuntime/DreamManager.cs b/OpenDreamRuntime/DreamManager.cs index 3ee77cf2c7..efcd580904 100644 --- a/OpenDreamRuntime/DreamManager.cs +++ b/OpenDreamRuntime/DreamManager.cs @@ -1,4 +1,5 @@ -using System.IO; +using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; using System.Text.Json; using DMCompiler.Bytecode; @@ -51,11 +52,15 @@ public sealed partial class DreamManager { private int _dreamObjectRefIdCounter; private DreamCompiledJson _compiledJson; + + [MemberNotNullWhen(true, nameof(_worldTickProc))] public bool Initialized { get; private set; } public GameTick InitializedTick { get; private set; } private ISawmill _sawmill = default!; + private DreamProc? _worldTickProc; + //TODO This arg is awful and temporary until RT supports cvar overrides in unit tests public void PreInitialize(string? jsonPath) { _sawmill = Logger.GetSawmill("opendream"); @@ -95,7 +100,10 @@ public void Update() { if (!Initialized) return; - _procScheduler.Process(); + _procScheduler.Process(true); + DreamThread.Run(_worldTickProc, WorldInstance, null); + _procScheduler.Process(false); + UpdateStat(); _dreamMapManager.UpdateTiles(); @@ -132,6 +140,8 @@ public bool LoadJson(string? jsonPath) { // Call /world/. This is an IMPLEMENTATION DETAIL and non-DMStandard should NOT be run here. WorldInstance.InitSpawn(new()); + _worldTickProc = WorldInstance.GetProc("Tick"); + if (_compiledJson.Globals is GlobalListJson jsonGlobals) { Globals = new DreamValue[jsonGlobals.GlobalCount]; GlobalNames = jsonGlobals.Names; diff --git a/OpenDreamRuntime/Procs/ProcScheduler.cs b/OpenDreamRuntime/Procs/ProcScheduler.cs index f4ae1566d5..0a9d292f3a 100644 --- a/OpenDreamRuntime/Procs/ProcScheduler.cs +++ b/OpenDreamRuntime/Procs/ProcScheduler.cs @@ -42,8 +42,10 @@ async Task Foo() { return task; } - public void Process() { - UpdateDelays(); + public void Process(bool updateDelays) { + if (updateDelays) { + UpdateDelays(); + } // Update all asynchronous tasks that have finished waiting and are ready to resume. // From 192753687579abda9925ebeefcf89f5c321bdf1b Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 1 Jan 2024 13:25:47 -0500 Subject: [PATCH 2/4] Fix always sleeping 1 tick too many --- OpenDreamRuntime/Procs/ProcScheduler.Delays.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenDreamRuntime/Procs/ProcScheduler.Delays.cs b/OpenDreamRuntime/Procs/ProcScheduler.Delays.cs index fd2033230d..aeb46b896d 100644 --- a/OpenDreamRuntime/Procs/ProcScheduler.Delays.cs +++ b/OpenDreamRuntime/Procs/ProcScheduler.Delays.cs @@ -53,7 +53,7 @@ public Task CreateDelayTicks(int ticks) { } var tcs = new TaskCompletionSource(); - _tickers.Add(new DelayTicker(tcs) { TicksLeft = ticks + 1 }); // Add 1 because it'll get decreased at the end of this tick + _tickers.Add(new DelayTicker(tcs) { TicksLeft = ticks }); return tcs.Task; } From 83c4696144a071fbd4d7f9132343e2d8da3c7fa6 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 1 Jan 2024 13:27:26 -0500 Subject: [PATCH 3/4] Comments about calls to world.Tick --- OpenDreamRuntime/DreamManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/OpenDreamRuntime/DreamManager.cs b/OpenDreamRuntime/DreamManager.cs index efcd580904..a8bb4bd60a 100644 --- a/OpenDreamRuntime/DreamManager.cs +++ b/OpenDreamRuntime/DreamManager.cs @@ -100,11 +100,12 @@ public void Update() { if (!Initialized) return; + // first process is allowed to update delays and run deferred tasks _procScheduler.Process(true); DreamThread.Run(_worldTickProc, WorldInstance, null); - _procScheduler.Process(false); + _procScheduler.Process(false); // nothing is allowed to run after /world/Tick() - UpdateStat(); + UpdateStat(); // ... except for Stat _dreamMapManager.UpdateTiles(); WorldInstance.SetVariableValue("cpu", WorldInstance.GetVariable("tick_usage")); From f1f348bbb8a7a96c64853fa7a34f29016b627f49 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 1 Jan 2024 13:27:33 -0500 Subject: [PATCH 4/4] Add world.Tick tests --- .../DMProject/Tests/Tree/Global/World/tick.dm | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Content.Tests/DMProject/Tests/Tree/Global/World/tick.dm diff --git a/Content.Tests/DMProject/Tests/Tree/Global/World/tick.dm b/Content.Tests/DMProject/Tests/Tree/Global/World/tick.dm new file mode 100644 index 0000000000..6e6b6b96ee --- /dev/null +++ b/Content.Tests/DMProject/Tests/Tree/Global/World/tick.dm @@ -0,0 +1,33 @@ + +//# issue 361 + +var/counter = 0 +var/counter_updated = FALSE + +/world/Tick() + ASSERT(!counter_updated) + var/updated = ++counter + counter_updated = TRUE + sleep(world.tick_lag) + // this should run before the next world.Tick() + ASSERT(!counter_updated) + ASSERT(updated == counter) + +/proc/RunTest() + sleep(world.tick_lag) // at time of writing, initial call to DreamThread.Run happens before the first tick and it's fucky + counter_updated = FALSE + for(var/i in 1 to 100) + var/last_read = counter + sleep(-1) + ASSERT(!counter_updated) + ASSERT(last_read == counter) + sleep(0) + ASSERT(!counter_updated) + ASSERT(last_read == counter) + sleep(world.tick_lag) + var/expected = last_read + 1 + var/actual = counter + ASSERT(counter_updated) + if(expected != actual) + CRASH("Expected: [expected] Actual: [actual]!") + counter_updated = FALSE