Files
Christopher Wiebe b451f83174 Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback
anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md).
All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:40:51 -07:00

69 lines
1.9 KiB
C#

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Theriapolis.Game.Screens;
/// <summary>
/// Manages a stack of IScreen instances.
/// Only the top screen receives Update and Draw calls.
/// Push/Pop are deferred to the start of the next frame to avoid mid-loop mutation.
/// </summary>
public sealed class ScreenManager
{
private readonly Game1 _game;
private readonly Stack<IScreen> _stack = new();
private IScreen? _pendingPush;
private int _pendingPops; // count rather than bool — multiple Pops queued in one frame all apply
public ScreenManager(Game1 game)
{
_game = game;
}
public IScreen? Current => _stack.Count > 0 ? _stack.Peek() : null;
public void Push(IScreen screen)
{
_pendingPush = screen;
}
public void Pop()
{
_pendingPops++;
}
/// <summary>Process any pending push/pop, then update the current screen.</summary>
public void Update(GameTime gameTime)
{
// Apply deferred transitions — drain all pending pops before any push.
bool popped = false;
while (_pendingPops > 0 && _stack.Count > 0)
{
var top = _stack.Pop();
top.Deactivate();
_pendingPops--;
popped = true;
}
_pendingPops = 0;
// Only call Reactivate once after a pop chain — not every frame.
if (popped && _stack.TryPeek(out var back)) back.Reactivate();
if (_pendingPush is not null)
{
_stack.TryPeek(out var cur);
cur?.Deactivate();
_pendingPush.Initialize(_game);
_stack.Push(_pendingPush);
_pendingPush = null;
}
Current?.Update(gameTime);
}
public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
Current?.Draw(gameTime, spriteBatch);
}
}