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() != null || selected.GetComponent() != 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; } } }