This repository has been archived on 2026-04-18. You can view files and clone it, but cannot push or open issues or pull requests.
Virtual-Memory-Demo/Assets/Scripts/UI/SimulationUIController.cs

459 lines
14 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using VMdemo.Core;
using VMdemo.Simulation;
namespace VMdemo.UI
{
public class SimulationUIController : MonoBehaviour
{
[Header("Sub Views")]
[SerializeField] private SimulationConfigInputView configInputView;
[SerializeField] private SimulationControlView controlView;
[SerializeField] private SimulationDashboardView dashboardView;
[SerializeField] private SimulationTablesView tablesView;
[SerializeField] private SimulationTraceView traceView;
[SerializeField] private SimulationTimelineView detailTimelineView;
[SerializeField] private SimulationProcessHeaderView processHeaderView;
[SerializeField] private StepFlowAnimator flowAnimator;
[SerializeField] private SimulationDetailDrawerView detailDrawerView;
[Header("Playback")]
[SerializeField] private float playIntervalSeconds = 0.3f;
private TranslatorEngine _engine;
private Coroutine _playRoutine;
private SimulationConfig _activeConfig;
private void Awake()
{
BindControlEvents();
controlView?.SetPlaying(false);
InitializeEngineIfNeeded();
RefreshUI(accessCompleted: false);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
HandleStepHotkey();
}
}
private void OnDisable()
{
StopPlayRoutine();
}
public void OpenConfigPanel()
{
if (configInputView == null)
{
return;
}
configInputView.PopulateFromConfig(_activeConfig ?? new SimulationConfig());
configInputView.SetPanelVisible(true);
dashboardView?.SetError(string.Empty);
}
public void ToggleDetailDrawer()
{
detailDrawerView?.ToggleDrawer();
}
public void OpenDetailDrawer()
{
detailDrawerView?.OpenDrawer();
}
public void CloseDetailDrawer()
{
detailDrawerView?.CloseDrawer();
}
public void ShowTablesTab()
{
detailDrawerView?.ShowTablesTab();
detailDrawerView?.OpenDrawer();
}
public void ShowEventsTab()
{
detailDrawerView?.ShowEventsTab();
detailDrawerView?.OpenDrawer();
}
public void CloseConfigPanel()
{
configInputView?.SetPanelVisible(false);
}
public void SaveConfigAndClosePanel()
{
if (configInputView == null)
{
dashboardView?.SetError("缺少 SimulationConfigInputView 引用。");
return;
}
if (!configInputView.TryBuildConfig(out var config, out var error))
{
dashboardView?.SetError(error);
return;
}
dashboardView?.SetError(string.Empty);
if (!IsSameConfig(_activeConfig, config))
{
ApplyConfig(config, appendInitLog: true, clearTimeline: true);
}
else
{
AppendEvent("配置未变更", "配置未变更,保留当前引擎。");
}
configInputView.SetPanelVisible(false);
}
public void CancelConfigAndClosePanel()
{
if (configInputView == null)
{
return;
}
configInputView.PopulateFromConfig(_activeConfig ?? new SimulationConfig());
configInputView.SetPanelVisible(false);
dashboardView?.SetError(string.Empty);
AppendEvent("已取消配置", "已取消配置面板修改,保留当前引擎。");
}
private void BindControlEvents()
{
if (controlView == null)
{
return;
}
controlView.StepClicked += HandleStepClicked;
controlView.PlayClicked += HandlePlayClicked;
controlView.PauseClicked += HandlePauseClicked;
controlView.ResetClicked += HandleResetClicked;
}
private void InitializeEngineIfNeeded()
{
if (_engine != null)
{
return;
}
string initialError = string.Empty;
if (configInputView != null)
{
if (configInputView.TryBuildConfig(out var viewConfig, out var viewError))
{
dashboardView?.SetError(string.Empty);
ApplyConfig(viewConfig, appendInitLog: true, clearTimeline: true);
return;
}
initialError = viewError;
}
var fallback = new SimulationConfig();
if (configInputView != null)
{
configInputView.PopulateFromConfig(fallback);
}
ApplyConfig(fallback, appendInitLog: true, clearTimeline: true);
if (!string.IsNullOrEmpty(initialError))
{
dashboardView?.SetError(initialError);
}
}
private void ApplyConfig(SimulationConfig config, bool appendInitLog, bool clearTimeline)
{
if (config == null)
{
config = new SimulationConfig();
}
config.ApplyFixedCoreSpec();
StopPlayRoutine();
controlView?.SetPlaying(false);
_activeConfig = CloneConfig(config);
_engine = new TranslatorEngine(_activeConfig);
if (clearTimeline)
{
detailTimelineView?.Clear();
}
if (appendInitLog)
{
AppendEvent("模拟已就绪", "已按新配置初始化模拟。");
}
RefreshUI(accessCompleted: false);
}
private IEnumerator PlayRoutine()
{
while (_engine != null)
{
if (_activeConfig != null &&
_engine.Stats.TotalAccess >= _activeConfig.accessCount &&
_engine.State.CurrentStep == TranslationStep.GenerateVA)
{
AppendEvent("连续播放结束", "连续播放达到访问次数上限。");
break;
}
var accessCompleted = _engine.StepOnce();
RefreshUI(accessCompleted);
if (accessCompleted)
{
var state = _engine.State;
AppendEvent(
BuildAccessCompactMessage(state),
$"访问#{state.CurrentRound} 虚拟地址VA={SimulationUIFormatter.FormatAddressHex(state.CurrentVirtualAddress, _engine.VaBits)} -> " +
$"物理地址PA={SimulationUIFormatter.FormatAddressHex(state.CurrentPhysicalAddress, _engine.VaBits)} " +
$"TLB命中={state.IsTlbHit} 页表命中={state.IsPageTableHit} 缺页={state.IsPageFault} 开销={state.CurrentCost}");
}
yield return new WaitForSeconds(playIntervalSeconds);
}
_playRoutine = null;
controlView?.SetPlaying(false);
}
private void RefreshUI(bool accessCompleted)
{
if (_engine == null)
{
dashboardView?.ShowNotInitialized();
tablesView?.ShowNotInitialized();
traceView?.ShowNotInitialized();
flowAnimator?.ResetVisual();
processHeaderView?.RenderStep(null);
return;
}
dashboardView?.Render(_engine);
tablesView?.Render(_engine);
traceView?.Render(_engine);
flowAnimator?.AnimateStep(_engine.State.CurrentStep, _engine.State.IsPageFault, accessCompleted);
processHeaderView?.RenderStep(_engine.State);
}
private void AppendStepLog(bool accessCompleted)
{
if (_engine == null)
{
return;
}
var state = _engine.State;
if (accessCompleted)
{
AppendEvent(
$"访问#{state.CurrentRound} 完成({BuildResultFlags(state)}",
$"访问#{state.CurrentRound} 完成 | " +
$"TLB命中={state.IsTlbHit} 页表命中={state.IsPageTableHit} 缺页={state.IsPageFault} 开销={state.CurrentCost}\n" +
$"分支原因:{state.LastDecisionReason}");
return;
}
var stepZh = TranslationStepDisplay.ToChinese(state.CurrentStep);
AppendEvent($"步骤:{stepZh}", $"步骤 -> {stepZh}\n分支原因{state.LastDecisionReason}");
}
private void AppendEvent(string compact, string detail)
{
detailTimelineView?.Append(detail);
processHeaderView?.SetEvent(compact);
}
private void StopPlayRoutine()
{
if (_playRoutine == null)
{
return;
}
StopCoroutine(_playRoutine);
_playRoutine = null;
}
private void HandleStepClicked()
{
InitializeEngineIfNeeded();
if (_engine == null)
{
return;
}
var accessCompleted = _engine.StepOnce();
RefreshUI(accessCompleted);
AppendStepLog(accessCompleted);
}
private void HandlePlayClicked()
{
InitializeEngineIfNeeded();
if (_engine == null || _playRoutine != null)
{
return;
}
_playRoutine = StartCoroutine(PlayRoutine());
controlView?.SetPlaying(true);
AppendEvent("开始连续播放", "已开始连续播放。");
}
private void HandleStepHotkey()
{
if (IsStepHotkeyBlocked())
{
return;
}
HandleStepClicked();
}
private void HandlePauseClicked()
{
StopPlayRoutine();
controlView?.SetPlaying(false);
AppendEvent("暂停播放", "已暂停连续播放。");
}
private void HandleResetClicked()
{
StopPlayRoutine();
if (_engine != null)
{
_engine.Reset();
}
detailTimelineView?.Clear();
RefreshUI(accessCompleted: false);
controlView?.SetPlaying(false);
AppendEvent("已重置", "模拟已重置。");
}
private bool IsStepHotkeyBlocked()
{
if (_playRoutine != null)
{
return true;
}
if (configInputView != null && configInputView.IsPanelVisible)
{
return true;
}
return IsTypingInInputField();
}
private static bool IsTypingInInputField()
{
var eventSystem = EventSystem.current;
if (eventSystem == null)
{
return false;
}
var selected = eventSystem.currentSelectedGameObject;
if (selected == null)
{
return false;
}
return selected.GetComponent<TMP_InputField>() != null ||
selected.GetComponent<InputField>() != null;
}
private static string BuildAccessCompactMessage(SimulationState state)
{
var flagZh = state.IsPageFault ? "缺页" : (state.IsTlbHit ? "TLB命中" : (state.IsPageTableHit ? "页表命中" : "未命中"));
return $"访问#{state.CurrentRound} {flagZh}";
}
private static string BuildResultFlags(SimulationState state)
{
if (state.IsPageFault)
{
return "缺页";
}
if (state.IsTlbHit)
{
return "TLB命中";
}
if (state.IsPageTableHit)
{
return "页表命中";
}
return "未知";
}
private static SimulationConfig CloneConfig(SimulationConfig source)
{
if (source == null)
{
return new SimulationConfig();
}
return new SimulationConfig
{
machineBits = source.machineBits,
vaBits = source.vaBits,
pageSizeKB = source.pageSizeKB,
physicalMemoryMB = source.physicalMemoryMB,
tlbEntries = source.tlbEntries,
accessCount = source.accessCount,
pageFaultPenalty = source.pageFaultPenalty,
addressGenerationMode = source.addressGenerationMode,
arrayLengthBytes = source.arrayLengthBytes,
arrayElementBytes = source.arrayElementBytes,
arrayBaseAddress = source.arrayBaseAddress
};
}
private static bool IsSameConfig(SimulationConfig a, SimulationConfig b)
{
if (a == null || b == null)
{
return false;
}
return a.machineBits == b.machineBits &&
a.vaBits == b.vaBits &&
a.pageSizeKB == b.pageSizeKB &&
a.physicalMemoryMB == b.physicalMemoryMB &&
a.tlbEntries == b.tlbEntries &&
a.accessCount == b.accessCount &&
a.pageFaultPenalty == b.pageFaultPenalty &&
a.addressGenerationMode == b.addressGenerationMode &&
a.arrayLengthBytes == b.arrayLengthBytes &&
a.arrayElementBytes == b.arrayElementBytes &&
a.arrayBaseAddress == b.arrayBaseAddress;
}
}
}