engine: retain History and wait presentation

This commit is contained in:
gamer147
2026-07-30 19:21:38 -04:00
parent e5b4db5eff
commit bb227ea106
14 changed files with 585 additions and 95 deletions

View File

@@ -86,15 +86,28 @@ public interface IHost
GfxState gfx, AdvTextLayoutPresentationBinding binding)
=> ClearRenderedAdvTextLayout(binding.LayoutSlot);
void RenderTextHistory(AdvTextHistoryRenderBatch batch) { }
bool RenderTextHistory(
GfxState gfx,
AdvTextLayoutPresentationBinding binding,
AdvTextHistoryRenderBatch batch)
{
RenderTextHistory(batch);
return false;
}
// History render batches are transient bindings, unlike the retained backlog itself. HISTORY.BIN's
// recording re-enable at exit ends that presentation and drops every bound target layout.
void EndTextHistoryPresentation() { }
void EndTextHistoryPresentation(GfxState gfx) => EndTextHistoryPresentation();
int MessageWindowAlphaSetting => 0;
void SetMessageWindowAlphaSetting(int value) { }
void FillSurfaceRect(SurfaceRectFill fill) { }
void CopySurfaceRect(SurfaceRectCopy copy) { }
void PresentObjectRange(GfxState gfx, long firstHandle, long count) { }
void ConfigureAdvWaitIndicator(AdvWaitIndicatorConfig config) { }
// Op 0x212 supplies the ordinary retained handle used to publish the configured atlas cell.
void BindAdvWaitIndicator(
AdvTextLayoutPresentationBinding binding,
AdvTextLayoutSnapshot layout) { }
// Op 0x1ce explicitly starts/stops the same animated marker that op 0x72 starts for an ADV wait.
void SetAdvWaitIndicatorEnabled(bool enabled) { }
// Op 0x20a republishes one retained ADV text layout and includes the current marker frame when active.

View File

@@ -207,8 +207,8 @@ public sealed class AdvTextHistory
/// <summary>
/// Resolve layouts whose complete native retained-glyph handle interval is covered by one op-0x1f7
/// erase. The Label backend collapses those glyph objects into live text runs, so full native range
/// teardown removes the corresponding detached presentation atomically.
/// erase. Hosts use this notification to discard fallback metadata or preserve exact transient records
/// for a following native op-0x20a republication, according to the active presentation path.
/// </summary>
public IReadOnlyList<int> LayoutsCoveredByTextObjectErase(long firstHandle, long count)
{

View File

@@ -0,0 +1,95 @@
using Age.Engine.Hosting;
using Age.Engine.Model;
namespace Age.Engine.Text;
/// <summary>
/// Transient retained-object projection of one ADV layout's animated input-wait atlas.
/// The script owns the source surface and object handle; this object owns only the current binding.
/// </summary>
public sealed class RetainedAdvWaitIndicatorPresentation
{
private int _publishedFrame = -1;
public RetainedAdvWaitIndicatorPresentation(
AdvWaitIndicatorConfig config,
AdvTextLayoutPresentationBinding binding,
int layoutOriginX,
int layoutOriginY)
{
if (config.LayoutSlot != binding.LayoutSlot)
throw new ArgumentException(
"Wait-indicator configuration and retained binding must select the same layout.");
if (binding.WaitIndicatorObjectHandle < 0)
throw new ArgumentException(
"A retained ADV wait indicator requires a nonnegative object handle.",
nameof(binding));
if (config.SurfaceSlot < 0 || config.CellWidth <= 0 || config.CellHeight <= 0)
throw new ArgumentOutOfRangeException(nameof(config));
Config = config;
Binding = binding;
LayoutOriginX = layoutOriginX;
LayoutOriginY = layoutOriginY;
}
public AdvWaitIndicatorConfig Config { get; }
public AdvTextLayoutPresentationBinding Binding { get; }
public int LayoutOriginX { get; }
public int LayoutOriginY { get; }
public int PublishedFrame => _publishedFrame;
public bool IsPublished => _publishedFrame >= 0;
/// <summary>Publish a changed source cell, or erase the retained handle when inactive.</summary>
public bool Update(GfxState gfx, long elapsedMs, bool visible)
{
ArgumentNullException.ThrowIfNull(gfx);
if (!visible)
{
if (!IsPublished) return false;
Erase(gfx);
return true;
}
int frame = Config.FrameAt(elapsedMs);
if (frame == _publishedFrame) return false;
Bind(gfx, frame);
return true;
}
/// <summary>Recreate the current frame after script-owned range teardown/publication.</summary>
public bool Republish(GfxState gfx, long elapsedMs, bool visible)
{
ArgumentNullException.ThrowIfNull(gfx);
if (!visible)
{
if (!IsPublished) return false;
Erase(gfx);
return true;
}
Bind(gfx, Config.FrameAt(elapsedMs));
return true;
}
public void Erase(GfxState gfx)
{
ArgumentNullException.ThrowIfNull(gfx);
gfx.EraseRange(Binding.WaitIndicatorObjectHandle, 1);
_publishedFrame = -1;
}
private void Bind(GfxState gfx, int frame)
{
gfx.BindDraw(
Binding.WaitIndicatorObjectHandle,
Config.SurfaceSlot,
checked(Config.SourceX + frame * Config.CellWidth),
Config.SourceY,
Config.CellWidth,
Config.CellHeight,
checked(LayoutOriginX + Config.X),
checked(LayoutOriginY + Config.Y));
_publishedFrame = frame;
}
}

View File

@@ -2347,7 +2347,7 @@ public sealed class VirtualMachine
{
bool enabled = Read(a[0]) == 1;
TextHistory.SetRecordingEnabled(enabled);
if (enabled) _host.EndTextHistoryPresentation();
if (enabled) _host.EndTextHistoryPresentation(Gfx);
}
return pc + 1;
case "append-text-history-metadata": // 0x1d2: (metadata type, value)
@@ -2370,11 +2370,17 @@ public sealed class VirtualMachine
int flags = (int)Read(a[2]);
if ((flags & 4) == 0 && TextHistory.TryBuildRenderBatch(
(int)Read(a[0]), (int)Read(a[1]), flags, Read(a[3]), Read(a[4]), out var batch))
_host.RenderTextHistory(batch with
{
batch = batch with
{
// Native History uses the text manager's current leading, not a retained-record field.
Style = batch.Style with { LineSpacing = _advTextStyle.LineSpacing }
});
};
_host.RenderTextHistory(
Gfx,
TextHistory.GetPresentationBinding(batch.LayoutSlot),
batch);
}
return pc + 1;
}
case "u0041BB90":
@@ -2820,7 +2826,13 @@ public sealed class VirtualMachine
return pc + 1;
case "set-adv-wait-indicator-handle": // 0x212 (layout)(retained handle)
{
TextHistory.SetWaitIndicatorObjectHandle((int)Read(a[0]), Read(a[1]));
int requestedSlot = (int)Read(a[0]);
TextHistory.SetWaitIndicatorObjectHandle(requestedSlot, Read(a[1]));
AdvTextLayoutSnapshot layout =
TextHistory.GetLayoutSnapshot(requestedSlot);
_host.BindAdvWaitIndicator(
TextHistory.GetPresentationBinding(layout.Slot),
layout);
return pc + 1;
}
case "set-adv-text-object-range": // 0x213 (layout)(first handle)(count)