Archived
finish(maybe...)
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using VMdemo.Core;
|
||||
using System;
|
||||
|
||||
namespace VMdemo.UI
|
||||
{
|
||||
public class SimulationConfigInputView : MonoBehaviour
|
||||
{
|
||||
[Header("Fixed Core Spec")] [SerializeField]
|
||||
private TMP_Text fixedCoreSpecText;
|
||||
|
||||
[SerializeField] private TMP_InputField accessCountInput;
|
||||
[SerializeField] private TMP_InputField pageFaultPenaltyInput;
|
||||
[SerializeField] private TMP_Dropdown addressModeDropdown;
|
||||
[SerializeField] private TMP_InputField arrayLengthBytesInput;
|
||||
[SerializeField] private TMP_InputField arrayElementBytesInput;
|
||||
[SerializeField] private TMP_InputField arrayBaseAddressInput;
|
||||
|
||||
[SerializeField] private Button saveButton;
|
||||
[SerializeField] private Button cancelButton;
|
||||
[SerializeField] private GameObject panelRoot;
|
||||
|
||||
public event Action SaveClicked;
|
||||
public event Action CancelClicked;
|
||||
public bool IsPanelVisible => panelRoot != null ? panelRoot.activeInHierarchy : gameObject.activeInHierarchy;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
BindButtons();
|
||||
ApplyFixedCoreSpecVisual();
|
||||
}
|
||||
|
||||
public bool TryBuildConfig(out SimulationConfig config, out string errorMessage)
|
||||
{
|
||||
config = new SimulationConfig
|
||||
{
|
||||
machineBits = SimulationConfig.FixedMachineBits,
|
||||
vaBits = SimulationConfig.FixedVaBits,
|
||||
pageSizeKB = SimulationConfig.FixedPageSizeKB,
|
||||
physicalMemoryMB = SimulationConfig.FixedPhysicalMemoryMB,
|
||||
tlbEntries = SimulationConfig.FixedTlbEntries,
|
||||
accessCount = ParseInt(accessCountInput, 100),
|
||||
pageFaultPenalty = ParseInt(pageFaultPenaltyInput, 100),
|
||||
addressGenerationMode = ReadAddressGenerationMode(),
|
||||
arrayLengthBytes = ParseInt(arrayLengthBytesInput, 256),
|
||||
arrayElementBytes = ParseInt(arrayElementBytesInput, 4),
|
||||
arrayBaseAddress = ParseInt(arrayBaseAddressInput, 0)
|
||||
};
|
||||
config.ApplyFixedCoreSpec();
|
||||
|
||||
return ConfigValidator.TryValidate(config, out errorMessage);
|
||||
}
|
||||
|
||||
public void PopulateFromConfig(SimulationConfig config)
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetInputText(accessCountInput, config.accessCount);
|
||||
SetInputText(pageFaultPenaltyInput, config.pageFaultPenalty);
|
||||
SetAddressGenerationMode(config.addressGenerationMode);
|
||||
SetInputText(arrayLengthBytesInput, config.arrayLengthBytes);
|
||||
SetInputText(arrayElementBytesInput, config.arrayElementBytes);
|
||||
SetInputText(arrayBaseAddressInput, config.arrayBaseAddress);
|
||||
|
||||
ApplyFixedCoreSpecVisual();
|
||||
}
|
||||
|
||||
public void SetPanelVisible(bool visible)
|
||||
{
|
||||
if (panelRoot != null)
|
||||
{
|
||||
panelRoot.SetActive(visible);
|
||||
if (visible)
|
||||
{
|
||||
ApplyFixedCoreSpecVisual();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
gameObject.SetActive(visible);
|
||||
if (visible)
|
||||
{
|
||||
ApplyFixedCoreSpecVisual();
|
||||
}
|
||||
}
|
||||
|
||||
private void BindButtons()
|
||||
{
|
||||
if (saveButton != null)
|
||||
{
|
||||
saveButton.onClick.RemoveAllListeners();
|
||||
saveButton.onClick.AddListener(() => SaveClicked?.Invoke());
|
||||
}
|
||||
|
||||
if (cancelButton != null)
|
||||
{
|
||||
cancelButton.onClick.RemoveAllListeners();
|
||||
cancelButton.onClick.AddListener(() => CancelClicked?.Invoke());
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyFixedCoreSpecVisual()
|
||||
{
|
||||
if (fixedCoreSpecText != null)
|
||||
{
|
||||
fixedCoreSpecText.text =
|
||||
$"{SimulationConfig.FixedMachineBits}位机\n" +
|
||||
$"VA {SimulationConfig.FixedVaBits}位\n" +
|
||||
$"页大小 {SimulationConfig.FixedPageSizeKB}KB\n" +
|
||||
$"物理内存 {SimulationConfig.FixedPhysicalMemoryMB}MB\n" +
|
||||
$"TLB {SimulationConfig.FixedTlbEntries}条全相联";
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetInputText(TMP_InputField input, int value)
|
||||
{
|
||||
if (input != null)
|
||||
{
|
||||
input.text = value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetInteractable(Selectable selectable, bool interactable)
|
||||
{
|
||||
if (selectable != null)
|
||||
{
|
||||
selectable.interactable = interactable;
|
||||
}
|
||||
}
|
||||
|
||||
private AddressGenerationMode ReadAddressGenerationMode()
|
||||
{
|
||||
if (addressModeDropdown == null || addressModeDropdown.options == null ||
|
||||
addressModeDropdown.options.Count == 0)
|
||||
{
|
||||
return AddressGenerationMode.SequentialArrayLoop;
|
||||
}
|
||||
|
||||
// Preferred convention:
|
||||
// index 0 = RandomUniform, index 1 = SequentialArrayLoop.
|
||||
if (addressModeDropdown.options.Count >= 2)
|
||||
{
|
||||
return addressModeDropdown.value == 0
|
||||
? AddressGenerationMode.RandomUniform
|
||||
: AddressGenerationMode.SequentialArrayLoop;
|
||||
}
|
||||
|
||||
var option = addressModeDropdown.options[addressModeDropdown.value].text;
|
||||
if (IsRandomOption(option))
|
||||
{
|
||||
return AddressGenerationMode.RandomUniform;
|
||||
}
|
||||
|
||||
if (IsSequentialOption(option))
|
||||
{
|
||||
return AddressGenerationMode.SequentialArrayLoop;
|
||||
}
|
||||
|
||||
return AddressGenerationMode.SequentialArrayLoop;
|
||||
}
|
||||
|
||||
private void SetAddressGenerationMode(AddressGenerationMode mode)
|
||||
{
|
||||
if (addressModeDropdown == null || addressModeDropdown.options == null ||
|
||||
addressModeDropdown.options.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (addressModeDropdown.options.Count >= 2)
|
||||
{
|
||||
addressModeDropdown.value = mode == AddressGenerationMode.RandomUniform ? 0 : 1;
|
||||
addressModeDropdown.RefreshShownValue();
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < addressModeDropdown.options.Count; i++)
|
||||
{
|
||||
var option = addressModeDropdown.options[i].text;
|
||||
if ((mode == AddressGenerationMode.RandomUniform && IsRandomOption(option)) ||
|
||||
(mode == AddressGenerationMode.SequentialArrayLoop && IsSequentialOption(option)))
|
||||
{
|
||||
addressModeDropdown.value = i;
|
||||
addressModeDropdown.RefreshShownValue();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsRandomOption(string optionText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(optionText))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return optionText.Contains("Random") ||
|
||||
optionText.Contains("随机") ||
|
||||
optionText.Contains("rand");
|
||||
}
|
||||
|
||||
private static bool IsSequentialOption(string optionText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(optionText))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return optionText.Contains("Sequential") ||
|
||||
optionText.Contains("ArrayLoop") ||
|
||||
optionText.Contains("数组") ||
|
||||
optionText.Contains("顺序") ||
|
||||
optionText.Contains("for");
|
||||
}
|
||||
|
||||
private static int ParseInt(TMP_InputField input, int fallback)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return int.TryParse(input.text, out var value) ? value : fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93f3af00cf194cf4bf98f5df86f86ffc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace VMdemo.UI
|
||||
{
|
||||
public class SimulationControlView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Button stepButton;
|
||||
[SerializeField] private Button playButton;
|
||||
[SerializeField] private Button pauseButton;
|
||||
[SerializeField] private Button resetButton;
|
||||
|
||||
public event Action StepClicked;
|
||||
public event Action PlayClicked;
|
||||
public event Action PauseClicked;
|
||||
public event Action ResetClicked;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Bind();
|
||||
}
|
||||
|
||||
public void SetPlaying(bool isPlaying)
|
||||
{
|
||||
if (stepButton != null)
|
||||
{
|
||||
stepButton.interactable = !isPlaying;
|
||||
}
|
||||
|
||||
if (playButton != null)
|
||||
{
|
||||
playButton.interactable = !isPlaying;
|
||||
}
|
||||
|
||||
if (pauseButton != null)
|
||||
{
|
||||
pauseButton.interactable = isPlaying;
|
||||
}
|
||||
|
||||
if (resetButton != null)
|
||||
{
|
||||
resetButton.interactable = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Bind()
|
||||
{
|
||||
if (stepButton != null)
|
||||
{
|
||||
stepButton.onClick.RemoveAllListeners();
|
||||
stepButton.onClick.AddListener(() => StepClicked?.Invoke());
|
||||
}
|
||||
|
||||
if (playButton != null)
|
||||
{
|
||||
playButton.onClick.RemoveAllListeners();
|
||||
playButton.onClick.AddListener(() => PlayClicked?.Invoke());
|
||||
}
|
||||
|
||||
if (pauseButton != null)
|
||||
{
|
||||
pauseButton.onClick.RemoveAllListeners();
|
||||
pauseButton.onClick.AddListener(() => PauseClicked?.Invoke());
|
||||
}
|
||||
|
||||
if (resetButton != null)
|
||||
{
|
||||
resetButton.onClick.RemoveAllListeners();
|
||||
resetButton.onClick.AddListener(() => ResetClicked?.Invoke());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40ec73de0f6f4717a043fc64ff3c3b13
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using VMdemo.Simulation;
|
||||
|
||||
namespace VMdemo.UI
|
||||
{
|
||||
public class SimulationDashboardView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private TMP_Text stateSummaryText;
|
||||
[SerializeField] private TMP_Text statsText;
|
||||
[SerializeField] private TMP_Text errorText;
|
||||
|
||||
public void ShowNotInitialized()
|
||||
{
|
||||
SetText(stateSummaryText, "引擎:未初始化");
|
||||
SetText(statsText, "统计:N/A");
|
||||
}
|
||||
|
||||
public void Render(TranslatorEngine engine)
|
||||
{
|
||||
if (engine == null)
|
||||
{
|
||||
ShowNotInitialized();
|
||||
return;
|
||||
}
|
||||
|
||||
SetText(
|
||||
stateSummaryText,
|
||||
SimulationUIFormatter.FormatStateSummary(
|
||||
engine.State,
|
||||
engine.MachineBits,
|
||||
engine.VaBits,
|
||||
engine.PageSizeKB,
|
||||
engine.PhysicalMemoryMB,
|
||||
engine.TlbCache.Capacity));
|
||||
SetText(statsText, SimulationUIFormatter.FormatStats(engine.Stats));
|
||||
}
|
||||
|
||||
public void SetError(string message)
|
||||
{
|
||||
SetText(errorText, message);
|
||||
}
|
||||
|
||||
private static void SetText(TMP_Text target, string value)
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
target.text = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b68de53ac3e4c49a27b25bf5f91821b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,94 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace VMdemo.UI
|
||||
{
|
||||
public class SimulationDetailDrawerView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private GameObject drawerRoot;
|
||||
[SerializeField] private GameObject tablesTabRoot;
|
||||
[SerializeField] private GameObject eventsTabRoot;
|
||||
[SerializeField] private bool openOnStart;
|
||||
|
||||
[Header("Drawer Buttons")] [SerializeField]
|
||||
private Button tablesTabButton;
|
||||
|
||||
[SerializeField] private Button eventsTabButton;
|
||||
[SerializeField] private Button closeDrawerButton;
|
||||
|
||||
private bool _isOpen;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
BindButtons();
|
||||
_isOpen = openOnStart;
|
||||
ApplyDrawerState();
|
||||
|
||||
ShowTablesTab();
|
||||
}
|
||||
|
||||
public void ToggleDrawer()
|
||||
{
|
||||
_isOpen = !_isOpen;
|
||||
ApplyDrawerState();
|
||||
}
|
||||
|
||||
public void OpenDrawer()
|
||||
{
|
||||
_isOpen = true;
|
||||
ApplyDrawerState();
|
||||
}
|
||||
|
||||
public void CloseDrawer()
|
||||
{
|
||||
_isOpen = false;
|
||||
ApplyDrawerState();
|
||||
}
|
||||
|
||||
public void ShowTablesTab()
|
||||
{
|
||||
SetActiveSafe(tablesTabRoot, true);
|
||||
SetActiveSafe(eventsTabRoot, false);
|
||||
}
|
||||
|
||||
public void ShowEventsTab()
|
||||
{
|
||||
SetActiveSafe(tablesTabRoot, false);
|
||||
SetActiveSafe(eventsTabRoot, true);
|
||||
}
|
||||
|
||||
private void BindButtons()
|
||||
{
|
||||
if (tablesTabButton != null)
|
||||
{
|
||||
tablesTabButton.onClick.RemoveAllListeners();
|
||||
tablesTabButton.onClick.AddListener(ShowTablesTab);
|
||||
}
|
||||
|
||||
if (eventsTabButton != null)
|
||||
{
|
||||
eventsTabButton.onClick.RemoveAllListeners();
|
||||
eventsTabButton.onClick.AddListener(ShowEventsTab);
|
||||
}
|
||||
|
||||
if (closeDrawerButton != null)
|
||||
{
|
||||
closeDrawerButton.onClick.RemoveAllListeners();
|
||||
closeDrawerButton.onClick.AddListener(CloseDrawer);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyDrawerState()
|
||||
{
|
||||
SetActiveSafe(drawerRoot, _isOpen);
|
||||
}
|
||||
|
||||
private static void SetActiveSafe(GameObject target, bool active)
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
target.SetActive(active);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58e1bc0dfbbf46b7b64e6d6d74fd5f24
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using VMdemo.Simulation;
|
||||
|
||||
namespace VMdemo.UI
|
||||
{
|
||||
public class SimulationProcessHeaderView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private TMP_Text currentStepText;
|
||||
[SerializeField] private TMP_Text currentEventText;
|
||||
|
||||
public void RenderStep(SimulationState state)
|
||||
{
|
||||
if (state == null)
|
||||
{
|
||||
SetText(currentStepText, "步骤:N/A");
|
||||
return;
|
||||
}
|
||||
|
||||
SetText(currentStepText, $"步骤:{TranslationStepDisplay.ToChinese(state.CurrentStep)}");
|
||||
}
|
||||
|
||||
public void SetEvent(string message)
|
||||
{
|
||||
SetText(currentEventText, message);
|
||||
}
|
||||
|
||||
private static void SetText(TMP_Text target, string text)
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
target.text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71f3b95f31a74ef98ffd50e2f6435c20
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using VMdemo.Simulation;
|
||||
|
||||
namespace VMdemo.UI
|
||||
{
|
||||
public class SimulationTablesView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private TMP_Text tlbText;
|
||||
[SerializeField] private TMP_Text pageTableText;
|
||||
[SerializeField] private TMP_Text fifoText;
|
||||
|
||||
public void ShowNotInitialized()
|
||||
{
|
||||
SetText(tlbText, "TLB:N/A");
|
||||
SetText(pageTableText, "页表:N/A");
|
||||
SetText(fifoText, "FIFO:N/A");
|
||||
}
|
||||
|
||||
public void Render(TranslatorEngine engine)
|
||||
{
|
||||
if (engine == null)
|
||||
{
|
||||
ShowNotInitialized();
|
||||
return;
|
||||
}
|
||||
|
||||
var vpnBits = engine.VaBits - engine.OffsetBits;
|
||||
var pfnBits = SimulationUIFormatter.ComputeBitsForCount(engine.PhysicalMemoryManager.FrameCount);
|
||||
SetText(
|
||||
tlbText,
|
||||
SimulationUIFormatter.FormatTlbTable(
|
||||
engine.TlbCache.GetEntriesMostRecentFirst(),
|
||||
vpnBits,
|
||||
pfnBits));
|
||||
SetText(pageTableText, SimulationUIFormatter.FormatCurrentPageTableEntry(engine.State, engine.PageTable, engine.LastEviction));
|
||||
SetText(fifoText, SimulationUIFormatter.FormatFifoQueue(engine.PhysicalMemoryManager.GetFifoVpnOrder()));
|
||||
}
|
||||
|
||||
private static void SetText(TMP_Text target, string value)
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
target.text = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68f3ab1453614176a0f66589d29a55dd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace VMdemo.UI
|
||||
{
|
||||
public class SimulationTimelineView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private RectTransform logContainer;
|
||||
[SerializeField] private TMP_Text logItemPrefab;
|
||||
[SerializeField] private ScrollRect logScrollRect;
|
||||
[SerializeField] private int maxLogEntries = 200;
|
||||
|
||||
private readonly Queue<TMP_Text> _logItems = new Queue<TMP_Text>();
|
||||
|
||||
public void Append(string message)
|
||||
{
|
||||
if (logContainer == null || logItemPrefab == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = Instantiate(logItemPrefab, logContainer);
|
||||
item.gameObject.SetActive(true);
|
||||
item.text = message;
|
||||
item.ForceMeshUpdate();
|
||||
|
||||
var autoSize = item.GetComponent<TimelineLogItemAutoSize>();
|
||||
if (autoSize != null)
|
||||
{
|
||||
autoSize.RefreshLayout();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: adapt this text object's rect to preferred content size.
|
||||
var preferred = item.GetPreferredValues(item.text, logContainer.rect.width, 0f);
|
||||
var rect = item.rectTransform;
|
||||
rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, preferred.x);
|
||||
rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, preferred.y);
|
||||
}
|
||||
|
||||
_logItems.Enqueue(item);
|
||||
|
||||
while (_logItems.Count > Mathf.Max(1, maxLogEntries))
|
||||
{
|
||||
var old = _logItems.Dequeue();
|
||||
if (old != null)
|
||||
{
|
||||
Destroy(old.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(logContainer);
|
||||
Canvas.ForceUpdateCanvases();
|
||||
if (logScrollRect != null)
|
||||
{
|
||||
logScrollRect.verticalNormalizedPosition = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
while (_logItems.Count > 0)
|
||||
{
|
||||
var item = _logItems.Dequeue();
|
||||
if (item != null)
|
||||
{
|
||||
Destroy(item.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c65bcc42d5bf440ca80a93cc5a2f1fc1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,98 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using VMdemo.Simulation;
|
||||
|
||||
namespace VMdemo.UI
|
||||
{
|
||||
public class SimulationTraceView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private TMP_Text splitTraceText;
|
||||
[SerializeField] private TMP_Text pageTableTraceText;
|
||||
[SerializeField] private TMP_Text decisionReasonText;
|
||||
[SerializeField] private TMP_Text pathSummaryText;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
EnsureRichTextEnabled(splitTraceText);
|
||||
}
|
||||
|
||||
public void ShowNotInitialized()
|
||||
{
|
||||
SetText(splitTraceText, "地址拆分可视化:N/A");
|
||||
SetText(pageTableTraceText, "多级页表查询可视化:N/A");
|
||||
SetText(decisionReasonText, "分支原因:N/A");
|
||||
SetText(pathSummaryText, "路径摘要:N/A");
|
||||
}
|
||||
|
||||
public void Render(TranslatorEngine engine)
|
||||
{
|
||||
if (engine == null)
|
||||
{
|
||||
ShowNotInitialized();
|
||||
return;
|
||||
}
|
||||
|
||||
SetText(
|
||||
splitTraceText,
|
||||
SimulationUIFormatter.FormatAddressSplitTrace(
|
||||
engine.State,
|
||||
engine.VaBits,
|
||||
engine.OffsetBits,
|
||||
engine.L1Bits,
|
||||
engine.L2Bits));
|
||||
|
||||
SetText(
|
||||
pageTableTraceText,
|
||||
SimulationUIFormatter.FormatPageTableWalkTrace(engine.State, engine.PageTable));
|
||||
|
||||
SetText(
|
||||
decisionReasonText,
|
||||
string.IsNullOrEmpty(engine.State.LastDecisionReason)
|
||||
? "分支原因:暂无"
|
||||
: $"分支原因:{engine.State.LastDecisionReason}");
|
||||
|
||||
SetText(pathSummaryText, BuildPathSummary(engine.State));
|
||||
}
|
||||
|
||||
private static string BuildPathSummary(SimulationState state)
|
||||
{
|
||||
if (state == null)
|
||||
{
|
||||
return "路径摘要:N/A";
|
||||
}
|
||||
|
||||
if (state.IsTlbHit)
|
||||
{
|
||||
return "路径摘要:TLB命中 -> 直接合成PA";
|
||||
}
|
||||
|
||||
if (state.IsPageTableHit)
|
||||
{
|
||||
return "路径摘要:TLB未命中 -> 页表命中 -> 合成PA";
|
||||
}
|
||||
|
||||
if (state.IsPageFault)
|
||||
{
|
||||
return "路径摘要:TLB未命中 -> 页表未命中 -> 缺页处理 -> 合成PA";
|
||||
}
|
||||
|
||||
return "路径摘要:进行中";
|
||||
}
|
||||
|
||||
private static void SetText(TMP_Text target, string value)
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
target.text = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureRichTextEnabled(TMP_Text target)
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
target.richText = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f6f28922eb241649a4d14b8b6cc6bf7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,458 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac9c8cdc89fb6c14bbd38774897a7e26
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,324 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using VMdemo.Simulation;
|
||||
|
||||
namespace VMdemo.UI
|
||||
{
|
||||
public static class SimulationUIFormatter
|
||||
{
|
||||
private const string ColorVpn = "#4FC3F7";
|
||||
private const string ColorL1 = "#FFB74D";
|
||||
private const string ColorL2 = "#81C784";
|
||||
private const string ColorOffset = "#B0BEC5";
|
||||
|
||||
public static string FormatStateSummary(
|
||||
SimulationState state,
|
||||
int machineBits,
|
||||
int vaBits,
|
||||
int pageSizeKB,
|
||||
int physicalMemoryMB,
|
||||
int tlbEntries)
|
||||
{
|
||||
var va = FormatAddressHex(state.CurrentVirtualAddress, vaBits);
|
||||
var pa = FormatAddressHex(state.CurrentPhysicalAddress, vaBits);
|
||||
var stepText = TranslationStepDisplay.ToChinese(state.CurrentStep);
|
||||
return
|
||||
$"系统规格:{machineBits}位机\n" +
|
||||
$"虚拟地址有效位:{vaBits}位\n" +
|
||||
$"页大小:{pageSizeKB}KB\n" +
|
||||
$"物理内存:{physicalMemoryMB}MB\n" +
|
||||
$"TLB:{tlbEntries}条全相联\n";
|
||||
}
|
||||
|
||||
public static string FormatStats(StatsCollector stats)
|
||||
{
|
||||
return
|
||||
$"总访问={stats.TotalAccess}\n" +
|
||||
$"TLB命中率={stats.TlbHitRate:P2}\n" +
|
||||
$"页表命中率={stats.PageTableHitRate:P2}\n" +
|
||||
$"缺页次数={stats.PageFault}\n" +
|
||||
$"平均开销={stats.AvgCost:F2}";
|
||||
}
|
||||
|
||||
public static string FormatTlbTable(
|
||||
IReadOnlyList<TlbEntry> entries,
|
||||
int vpnBits,
|
||||
int pfnBits)
|
||||
{
|
||||
if (entries == null || entries.Count == 0)
|
||||
{
|
||||
return "TLB:空";
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("TLB(最近使用 -> 最久未用)");
|
||||
for (var i = 0; i < entries.Count; i++)
|
||||
{
|
||||
var entry = entries[i];
|
||||
sb.Append("#").Append(i + 1)
|
||||
.Append("VPN=").Append(FormatBitWidthHex(entry.Vpn, vpnBits))
|
||||
.Append(" -> PFN=").Append(FormatBitWidthHex(entry.Pfn, pfnBits))
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
return sb.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
public static string FormatCurrentPageTableEntry(
|
||||
SimulationState state,
|
||||
TwoLevelPageTable pageTable,
|
||||
EvictionInfo eviction)
|
||||
{
|
||||
if (state == null || pageTable == null)
|
||||
{
|
||||
return "页表:N/A";
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("当前页表项:");
|
||||
if (pageTable.TryGetEntry(state.CurrentAddressParts.L1Index, state.CurrentAddressParts.L2Index,
|
||||
out var entry))
|
||||
{
|
||||
sb.Append("存在=").Append(entry.Present)
|
||||
.Append(" 物理帧号PFN=").Append(FormatHex(entry.Pfn))
|
||||
.Append(" 脏位=").Append(entry.Dirty);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append("(缺失)");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
if (eviction.HasEvicted)
|
||||
{
|
||||
sb.Append("最近淘汰:虚拟页号VPN=").Append(FormatHex(eviction.EvictedVpn))
|
||||
.Append(" 物理帧号PFN=").Append(FormatHex(eviction.EvictedPfn));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append("最近淘汰:无");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string FormatFifoQueue(IReadOnlyList<ulong> fifoOrder)
|
||||
{
|
||||
if (fifoOrder == null || fifoOrder.Count == 0)
|
||||
{
|
||||
return "FIFO:空";
|
||||
}
|
||||
|
||||
var sb = new StringBuilder("FIFO(最早进入 -> 最新进入):");
|
||||
for (var i = 0; i < fifoOrder.Count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
sb.Append(" -> ");
|
||||
}
|
||||
|
||||
sb.Append(FormatHex(fifoOrder[i]));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string FormatAddressHex(ulong value, int vaBits)
|
||||
{
|
||||
return FormatBitWidthHex(value, vaBits);
|
||||
}
|
||||
|
||||
private static string FormatHex(ulong value)
|
||||
{
|
||||
return $"0x{value:X}";
|
||||
}
|
||||
|
||||
public static string FormatBitWidthHex(ulong value, int bits)
|
||||
{
|
||||
var digits = bits switch
|
||||
{
|
||||
8 => 2,
|
||||
16 => 4,
|
||||
24 => 6,
|
||||
32 => 8,
|
||||
48 => 12,
|
||||
64 => 16,
|
||||
_ => bits > 0 ? (bits + 3) / 4 : 1
|
||||
};
|
||||
|
||||
return $"0x{value.ToString($"X{digits}")}";
|
||||
}
|
||||
|
||||
public static int ComputeBitsForCount(ulong count)
|
||||
{
|
||||
if (count <= 1UL)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
var maxValue = count - 1UL;
|
||||
var bits = 0;
|
||||
while (maxValue > 0UL)
|
||||
{
|
||||
bits++;
|
||||
maxValue >>= 1;
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
public static string FormatAddressSplitTrace(
|
||||
SimulationState state,
|
||||
int vaBits,
|
||||
int offsetBits,
|
||||
int l1Bits,
|
||||
int l2Bits)
|
||||
{
|
||||
if (state == null)
|
||||
{
|
||||
return "地址拆分可视化:N/A";
|
||||
}
|
||||
|
||||
var vpnBits = vaBits - offsetBits;
|
||||
var vaHex = FormatAddressHex(state.CurrentVirtualAddress, vaBits);
|
||||
var vaBin = FormatBinary(state.CurrentVirtualAddress, vaBits);
|
||||
var vaBinColored = FormatVaBinaryWithFieldColors(state.CurrentVirtualAddress, vaBits, l1Bits, l2Bits, offsetBits);
|
||||
var vpnBin = FormatBinary(state.CurrentAddressParts.Vpn, vpnBits);
|
||||
var l1Bin = FormatBinary(state.CurrentAddressParts.L1Index, l1Bits);
|
||||
var l2Bin = FormatBinary(state.CurrentAddressParts.L2Index, l2Bits);
|
||||
var offBin = FormatBinary(state.CurrentAddressParts.Offset, offsetBits);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("地址拆分可视化");
|
||||
sb.AppendLine($"{vaHex})");
|
||||
sb.AppendLine($"{vaBinColored}");
|
||||
sb.AppendLine(
|
||||
$"{WrapColor("VPN", ColorVpn)} = {WrapColor("L1+L2", ColorVpn)}," +
|
||||
$"{WrapColor("L1", ColorL1)},{WrapColor("L2", ColorL2)},{WrapColor("Offset", ColorOffset)}");
|
||||
sb.AppendLine($"VPN({vpnBits}) = L1({l1Bits}) + L2({l2Bits}) | Offset({offsetBits})");
|
||||
sb.AppendLine($"VPN:{FormatBitWidthHex(state.CurrentAddressParts.Vpn, vpnBits)} ({WrapColor(vpnBin, ColorVpn)})");
|
||||
sb.AppendLine($"L1 索引:{FormatBitWidthHex(state.CurrentAddressParts.L1Index, l1Bits)} ({WrapColor(l1Bin, ColorL1)})");
|
||||
sb.AppendLine($"L2 索引:{FormatBitWidthHex(state.CurrentAddressParts.L2Index, l2Bits)} ({WrapColor(l2Bin, ColorL2)})");
|
||||
sb.Append($"Offset:{FormatBitWidthHex(state.CurrentAddressParts.Offset, offsetBits)} ({WrapColor(offBin, ColorOffset)})");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string FormatPageTableWalkTrace(SimulationState state, TwoLevelPageTable pageTable)
|
||||
{
|
||||
if (state == null || pageTable == null)
|
||||
{
|
||||
return "页表访问路径:N/A";
|
||||
}
|
||||
|
||||
var probe = pageTable.Probe(state.CurrentAddressParts.L1Index, state.CurrentAddressParts.L2Index);
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("多级页表查询可视化");
|
||||
sb.AppendLine(
|
||||
$"1) 先按 VPN 拆分:L1={state.CurrentAddressParts.L1Index}, L2={state.CurrentAddressParts.L2Index}");
|
||||
switch (probe.Status)
|
||||
{
|
||||
case PageTableProbeStatus.MissingL1Table:
|
||||
sb.Append("2) L1 未命中:对应二级页表不存在。");
|
||||
break;
|
||||
case PageTableProbeStatus.MissingL2Entry:
|
||||
sb.Append("2) L1 命中,L2 未命中:页表项不存在。");
|
||||
break;
|
||||
case PageTableProbeStatus.NotPresentEntry:
|
||||
sb.Append($"2) L1/L2 命中,但 present=false(PFN={FormatHex(probe.Entry.Pfn)})。");
|
||||
break;
|
||||
case PageTableProbeStatus.PresentEntry:
|
||||
sb.Append($"2) L1/L2 命中,present=true,得到 PFN={FormatHex(probe.Entry.Pfn)}。");
|
||||
break;
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string FormatBinary(ulong value, int bits)
|
||||
{
|
||||
if (bits <= 0)
|
||||
{
|
||||
return "0";
|
||||
}
|
||||
|
||||
var sb = new StringBuilder(bits + (bits / 4));
|
||||
for (var i = bits - 1; i >= 0; i--)
|
||||
{
|
||||
var bit = (value >> i) & 1UL;
|
||||
sb.Append(bit == 1UL ? '1' : '0');
|
||||
if (i > 0 && i % 4 == 0)
|
||||
{
|
||||
sb.Append('_');
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string FormatVaBinaryWithFieldColors(
|
||||
ulong value,
|
||||
int vaBits,
|
||||
int l1Bits,
|
||||
int l2Bits,
|
||||
int offsetBits)
|
||||
{
|
||||
if (vaBits <= 0)
|
||||
{
|
||||
return "0";
|
||||
}
|
||||
|
||||
var sb = new StringBuilder(vaBits * 10);
|
||||
string currentColor = null;
|
||||
|
||||
for (var i = vaBits - 1; i >= 0; i--)
|
||||
{
|
||||
var bitColor = ResolveBitColor(i, l1Bits, l2Bits, offsetBits);
|
||||
if (currentColor != bitColor)
|
||||
{
|
||||
if (currentColor != null)
|
||||
{
|
||||
sb.Append("</color>");
|
||||
}
|
||||
|
||||
sb.Append("<color=").Append(bitColor).Append(">");
|
||||
currentColor = bitColor;
|
||||
}
|
||||
|
||||
var bit = (value >> i) & 1UL;
|
||||
sb.Append(bit == 1UL ? '1' : '0');
|
||||
if (i > 0 && i % 4 == 0)
|
||||
{
|
||||
sb.Append('_');
|
||||
}
|
||||
}
|
||||
|
||||
if (currentColor != null)
|
||||
{
|
||||
sb.Append("</color>");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string ResolveBitColor(int bitIndex, int l1Bits, int l2Bits, int offsetBits)
|
||||
{
|
||||
var l1Start = offsetBits + l2Bits;
|
||||
if (bitIndex >= l1Start)
|
||||
{
|
||||
return ColorL1;
|
||||
}
|
||||
|
||||
if (bitIndex >= offsetBits)
|
||||
{
|
||||
return ColorL2;
|
||||
}
|
||||
|
||||
return ColorOffset;
|
||||
}
|
||||
|
||||
private static string WrapColor(string text, string colorHex)
|
||||
{
|
||||
return $"<color={colorHex}>{text}</color>";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d62c9fa502173a542a04294863667636
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using VMdemo.Simulation;
|
||||
|
||||
namespace VMdemo.UI
|
||||
{
|
||||
public class StepFlowAnimator : MonoBehaviour
|
||||
{
|
||||
[Serializable]
|
||||
public class StepNodeView
|
||||
{
|
||||
public TranslationStep step;
|
||||
public RectTransform target;
|
||||
public CanvasGroup canvasGroup;
|
||||
public Image background;
|
||||
}
|
||||
|
||||
[Header("Step Nodes")] [SerializeField]
|
||||
private List<StepNodeView> stepNodes = new List<StepNodeView>();
|
||||
|
||||
[Header("Colors")] [SerializeField] private Color idleColor = new Color(0.18f, 0.22f, 0.28f, 1f);
|
||||
[SerializeField] private Color activeColor = new Color(0.19f, 0.62f, 0.98f, 1f);
|
||||
[SerializeField] private Color faultColor = new Color(0.97f, 0.31f, 0.27f, 1f);
|
||||
|
||||
[Header("Animation")] [SerializeField] private float highlightDuration = 0.2f;
|
||||
[SerializeField] private float normalScale = 1f;
|
||||
[SerializeField] private float activeScale = 1.08f;
|
||||
[SerializeField] private float pulseScale = 1.04f;
|
||||
[SerializeField] private RectTransform accessPulseTarget;
|
||||
|
||||
private readonly Dictionary<TranslationStep, StepNodeView> _nodeMap =
|
||||
new Dictionary<TranslationStep, StepNodeView>();
|
||||
|
||||
private TranslationStep? _lastStep;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
BuildNodeMap();
|
||||
ResetVisual();
|
||||
}
|
||||
|
||||
public void ResetVisual()
|
||||
{
|
||||
DOTween.Kill(this);
|
||||
_lastStep = null;
|
||||
|
||||
foreach (var node in stepNodes)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node.target != null)
|
||||
{
|
||||
node.target.localScale = Vector3.one * normalScale;
|
||||
}
|
||||
|
||||
if (node.canvasGroup != null)
|
||||
{
|
||||
node.canvasGroup.alpha = 0.72f;
|
||||
}
|
||||
|
||||
if (node.background != null)
|
||||
{
|
||||
node.background.color = idleColor;
|
||||
}
|
||||
}
|
||||
|
||||
if (accessPulseTarget != null)
|
||||
{
|
||||
accessPulseTarget.localScale = Vector3.one;
|
||||
}
|
||||
}
|
||||
|
||||
public void AnimateStep(TranslationStep currentStep, bool isPageFault, bool accessCompleted)
|
||||
{
|
||||
BuildNodeMap();
|
||||
if (_lastStep.HasValue && _nodeMap.TryGetValue(_lastStep.Value, out var lastNode))
|
||||
{
|
||||
AnimateToIdle(lastNode);
|
||||
}
|
||||
|
||||
if (_nodeMap.TryGetValue(currentStep, out var currentNode))
|
||||
{
|
||||
AnimateToActive(currentNode, isPageFault && currentStep == TranslationStep.HandlePageFault);
|
||||
}
|
||||
|
||||
if (accessCompleted)
|
||||
{
|
||||
AnimateAccessCompletedPulse();
|
||||
}
|
||||
|
||||
_lastStep = currentStep;
|
||||
}
|
||||
|
||||
private void BuildNodeMap()
|
||||
{
|
||||
_nodeMap.Clear();
|
||||
foreach (var node in stepNodes)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_nodeMap.ContainsKey(node.step))
|
||||
{
|
||||
_nodeMap.Add(node.step, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AnimateToIdle(StepNodeView node)
|
||||
{
|
||||
if (node.target != null)
|
||||
{
|
||||
node.target.DOKill();
|
||||
node.target.DOScale(normalScale, highlightDuration).SetEase(Ease.OutQuad).SetTarget(this);
|
||||
}
|
||||
|
||||
if (node.canvasGroup != null)
|
||||
{
|
||||
node.canvasGroup.DOKill();
|
||||
node.canvasGroup.DOFade(0.72f, highlightDuration).SetEase(Ease.OutQuad).SetTarget(this);
|
||||
}
|
||||
|
||||
if (node.background != null)
|
||||
{
|
||||
node.background.DOKill();
|
||||
node.background.DOColor(idleColor, highlightDuration).SetEase(Ease.OutQuad).SetTarget(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void AnimateToActive(StepNodeView node, bool emphasizeFault)
|
||||
{
|
||||
if (node.target != null)
|
||||
{
|
||||
node.target.DOKill();
|
||||
node.target.DOScale(activeScale, highlightDuration).SetEase(Ease.OutBack).SetTarget(this);
|
||||
}
|
||||
|
||||
if (node.canvasGroup != null)
|
||||
{
|
||||
node.canvasGroup.DOKill();
|
||||
node.canvasGroup.DOFade(1f, highlightDuration).SetEase(Ease.OutQuad).SetTarget(this);
|
||||
}
|
||||
|
||||
if (node.background != null)
|
||||
{
|
||||
node.background.DOKill();
|
||||
var targetColor = emphasizeFault ? faultColor : activeColor;
|
||||
node.background.DOColor(targetColor, highlightDuration).SetEase(Ease.OutQuad).SetTarget(this);
|
||||
}
|
||||
|
||||
if (emphasizeFault && node.target != null)
|
||||
{
|
||||
node.target.DOPunchScale(new Vector3(0.07f, 0.07f, 0f), 0.28f, 6, 0.75f).SetTarget(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void AnimateAccessCompletedPulse()
|
||||
{
|
||||
if (accessPulseTarget == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
accessPulseTarget.DOKill();
|
||||
accessPulseTarget
|
||||
.DOScale(pulseScale, 0.12f)
|
||||
.SetEase(Ease.OutQuad)
|
||||
.SetLoops(2, LoopType.Yoyo)
|
||||
.SetTarget(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f36cd8ac00908594fb49c431d072c1bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace VMdemo.UI
|
||||
{
|
||||
[RequireComponent(typeof(RectTransform))]
|
||||
public class TimelineLogItemAutoSize : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private TMP_Text textComponent;
|
||||
[SerializeField] private float horizontalPadding = 20f;
|
||||
[SerializeField] private float verticalPadding = 12f;
|
||||
[SerializeField] private bool clampToParentWidth = true;
|
||||
[SerializeField] private float minWidth = 80f;
|
||||
[SerializeField] private float minHeight = 30f;
|
||||
|
||||
public void RefreshLayout()
|
||||
{
|
||||
var text = textComponent != null ? textComponent : GetComponent<TMP_Text>();
|
||||
var root = (RectTransform)transform;
|
||||
if (text == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float maxWidth = float.PositiveInfinity;
|
||||
if (clampToParentWidth && root.parent is RectTransform parentRect)
|
||||
{
|
||||
maxWidth = Mathf.Max(1f, parentRect.rect.width - horizontalPadding);
|
||||
}
|
||||
|
||||
var preferred = text.GetPreferredValues(text.text, maxWidth, 0f);
|
||||
var width = Mathf.Max(minWidth, preferred.x + horizontalPadding);
|
||||
var height = Mathf.Max(minHeight, preferred.y + verticalPadding);
|
||||
|
||||
if (clampToParentWidth && root.parent is RectTransform p)
|
||||
{
|
||||
width = Mathf.Min(width, Mathf.Max(minWidth, p.rect.width));
|
||||
}
|
||||
|
||||
root.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, width);
|
||||
root.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height);
|
||||
|
||||
var layoutElement = GetComponent<LayoutElement>();
|
||||
if (layoutElement != null)
|
||||
{
|
||||
layoutElement.preferredWidth = width;
|
||||
layoutElement.preferredHeight = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d594815f47f84f758ecf3e198de8d8de
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
using VMdemo.Simulation;
|
||||
|
||||
namespace VMdemo.UI
|
||||
{
|
||||
public static class TranslationStepDisplay
|
||||
{
|
||||
public static string ToChinese(TranslationStep step)
|
||||
{
|
||||
return step switch
|
||||
{
|
||||
TranslationStep.GenerateVA => "生成虚拟地址",
|
||||
TranslationStep.SplitVA => "拆分虚拟地址",
|
||||
TranslationStep.LookupTLB => "查询 TLB",
|
||||
TranslationStep.LookupPageTable => "查询页表",
|
||||
TranslationStep.HandlePageFault => "处理缺页",
|
||||
TranslationStep.ComposePA => "合成物理地址",
|
||||
TranslationStep.Finalize => "完成收尾",
|
||||
_ => step.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61f58553676e41d68ef5818d93ba09dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user