Prevent FIELD redraw flicker

This commit is contained in:
gamer147
2026-07-27 12:01:35 -04:00
parent a547fc4c11
commit ecf7289c7d
5 changed files with 344 additions and 86 deletions

View File

@@ -0,0 +1,74 @@
using System.Threading;
using System.Threading.Tasks;
using Age.Engine.Hosting;
using Xunit;
public class ScriptPresentationBarrierTests
{
[Fact]
public void ScriptBurstWithholdsPresentationUntilExit()
{
var barrier = new ScriptPresentationBarrier();
barrier.EnterScript();
Assert.False(barrier.TryEnterPresentation());
barrier.ExitScript();
Assert.True(barrier.TryEnterPresentation());
barrier.ExitPresentation();
}
[Fact]
public void NestedScriptsRemainOneAtomicBurst()
{
var barrier = new ScriptPresentationBarrier();
barrier.EnterScript();
barrier.EnterScript();
barrier.ExitScript();
Assert.False(barrier.TryEnterPresentation());
barrier.ExitScript();
Assert.True(barrier.TryEnterPresentation());
barrier.ExitPresentation();
}
[Fact]
public void ServiceBoundaryTemporarilyAllowsPresentation()
{
var barrier = new ScriptPresentationBarrier();
barrier.EnterScript();
bool suspended = barrier.SuspendScript();
Assert.True(suspended);
Assert.True(barrier.TryEnterPresentation());
barrier.ExitPresentation();
barrier.ResumeScript(suspended);
Assert.False(barrier.TryEnterPresentation());
barrier.ExitScript();
}
[Fact]
public async Task RenderThreadCannotEnterWhileVmThreadIsRebuilding()
{
var barrier = new ScriptPresentationBarrier();
using var entered = new ManualResetEventSlim();
using var release = new ManualResetEventSlim();
Task vmBurst = Task.Run(() =>
{
barrier.EnterScript();
entered.Set();
release.Wait();
barrier.ExitScript();
});
await Task.Run(entered.Wait);
Assert.False(barrier.TryEnterPresentation());
release.Set();
await vmBurst;
Assert.True(barrier.TryEnterPresentation());
barrier.ExitPresentation();
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Threading;
namespace Age.Engine.Hosting;
/// <summary>
/// Keeps one VM opcode burst atomic with respect to the interactive compositor. The VM owns the
/// write side while script code is running; the render thread samples through the read side only
/// after the VM reaches a presentation, sleep, or input service boundary.
/// </summary>
public sealed class ScriptPresentationBarrier
{
private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.NoRecursion);
private int _scriptDepth;
public void EnterScript()
{
if (_scriptDepth++ == 0)
_lock.EnterWriteLock();
}
public void ExitScript()
{
if (_scriptDepth <= 0)
throw new InvalidOperationException("No script burst is active.");
if (--_scriptDepth == 0)
_lock.ExitWriteLock();
}
/// <summary>Temporarily expose the completed burst state while a host service is parked.</summary>
public bool SuspendScript()
{
if (_scriptDepth <= 0 || !_lock.IsWriteLockHeld) return false;
_lock.ExitWriteLock();
return true;
}
public void ResumeScript(bool suspended)
{
if (suspended) _lock.EnterWriteLock();
}
public bool TryEnterPresentation()
=> !_lock.IsWriteLockHeld && _lock.TryEnterReadLock(0);
public void ExitPresentation() => _lock.ExitReadLock();
}