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 diff --git a/DMCompiler/DMStandard/Types/World.dm b/DMCompiler/DMStandard/Types/World.dm index 609109c1d8..1c2f040a5c 100644 --- a/DMCompiler/DMStandard/Types/World.dm +++ b/DMCompiler/DMStandard/Types/World.dm @@ -1,4 +1,4 @@ -/world +/world var/list/contents = null var/list/vars @@ -114,6 +114,10 @@ set opendream_unimplemented = TRUE return 0 + proc/Tick() + set waitfor = FALSE + return null + proc/ODHotReloadInterface() proc/ODHotReloadResource(var/file_name) diff --git a/OpenDreamRuntime/DreamManager.cs b/OpenDreamRuntime/DreamManager.cs index 48176de307..fb323bacb2 100644 --- a/OpenDreamRuntime/DreamManager.cs +++ b/OpenDreamRuntime/DreamManager.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Collections.Concurrent; using System.IO; using System.Linq; using System.Text.Json; @@ -60,11 +61,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"); @@ -104,8 +109,12 @@ public void Update() { if (!Initialized) return; - _procScheduler.Process(); - UpdateStat(); + // first process is allowed to update delays and run deferred tasks + _procScheduler.Process(true); + DreamThread.Run(_worldTickProc, WorldInstance, null); + _procScheduler.Process(false); // nothing is allowed to run after /world/Tick() + + UpdateStat(); // ... except for Stat _dreamMapManager.UpdateTiles(); DreamObjectSavefile.FlushAllUpdates(); WorldInstance.SetVariableValue("cpu", WorldInstance.GetVariable("tick_usage")); @@ -149,6 +158,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.Delays.cs b/OpenDreamRuntime/Procs/ProcScheduler.Delays.cs index 39d1fc3ca3..4c02625365 100644 --- a/OpenDreamRuntime/Procs/ProcScheduler.Delays.cs +++ b/OpenDreamRuntime/Procs/ProcScheduler.Delays.cs @@ -52,7 +52,7 @@ public Task CreateDelayTicks(int ticks) { } var tcs = new TaskCompletionSource(); - + InsertTask(new DelayTicker(tcs) { TicksAt = _gameTiming.CurTick.Value + (uint)ticks }); //safe cast because ticks is always positive here return tcs.Task; } 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. //