From 6bd885479c5174cf65b40b7cc38328cca5bbdbd7 Mon Sep 17 00:00:00 2001 From: veritas <52855341+abbccdd@users.noreply.github.com> Date: Wed, 2 Sep 2026 09:22:43 +0800 Subject: [PATCH] feat: integrate initial MFC simulation UI --- ElevatorSimulation/ElevatorSimulation.rc | Bin 10244 -> 15118 bytes ElevatorSimulation/ElevatorSimulationDlg.cpp | 383 +++++++++++++++++-- ElevatorSimulation/ElevatorSimulationDlg.h | 23 ++ ElevatorSimulation/Resource.h | 22 +- README.md | 15 +- 5 files changed, 412 insertions(+), 31 deletions(-) diff --git a/ElevatorSimulation/ElevatorSimulation.rc b/ElevatorSimulation/ElevatorSimulation.rc index ff77c66e5a7f6529392091820adaa8b077a3cc06..06c898b3df324f3c6cf386f5210892ef7f8c06fb 100644 GIT binary patch literal 15118 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 as4F@-#>j)!5a=@F$+iZHoBtRKu>$~AjXMqi diff --git a/ElevatorSimulation/ElevatorSimulationDlg.cpp b/ElevatorSimulation/ElevatorSimulationDlg.cpp index debf7b7..f46d32c 100644 --- a/ElevatorSimulation/ElevatorSimulationDlg.cpp +++ b/ElevatorSimulation/ElevatorSimulationDlg.cpp @@ -8,10 +8,66 @@ #include "ElevatorSimulationDlg.h" #include "afxdialogex.h" +#include +#include +#include +#include + #ifdef _DEBUG #define new DEBUG_NEW #endif +namespace +{ + const wchar_t* DirectionText(Direction direction) + { + switch (direction) + { + case Direction::Up: return L"↑"; + case Direction::Down: return L"↓"; + default: return L"-"; + } + } + + const wchar_t* ElevatorStateText(ElevatorState state) + { + switch (state) + { + case ElevatorState::MovingUp: return L"向上移动"; + case ElevatorState::MovingDown: return L"向下移动"; + case ElevatorState::Boarding: return L"上客"; + case ElevatorState::Alighting: return L"下客"; + case ElevatorState::Stopped: return L"停站"; + default: return L"空闲"; + } + } + + const wchar_t* SimulationStateText(SimulationState state) + { + switch (state) + { + case SimulationState::Ready: return L"Ready / 就绪"; + case SimulationState::Running: return L"Running / 运行中"; + case SimulationState::Paused: return L"Paused / 已暂停"; + case SimulationState::Finished: return L"Finished / 已结束"; + default: return L"Error / 未初始化"; + } + } + + CString Utf8ToCString(const std::string& text) + { + if (text.empty()) return CString(); + const int length = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), + static_cast(text.size()), nullptr, 0); + if (length <= 0) return CString(L"未知错误"); + CString result; + wchar_t* buffer = result.GetBuffer(length); + MultiByteToWideChar(CP_UTF8, 0, text.c_str(), static_cast(text.size()), buffer, length); + result.ReleaseBuffer(length); + return result; + } +} + // 用于应用程序“关于”菜单项的 CAboutDlg 对话框 @@ -59,12 +115,21 @@ CElevatorSimulationDlg::CElevatorSimulationDlg(CWnd* pParent /*=nullptr*/) void CElevatorSimulationDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); + DDX_Control(pDX, IDC_LIST_ELEVATORS, m_elevatorList); + DDX_Control(pDX, IDC_LIST_FLOORS, m_floorList); + DDX_Control(pDX, IDC_LIST_HALL_CALLS, m_hallCallList); } BEGIN_MESSAGE_MAP(CElevatorSimulationDlg, CDialogEx) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() + ON_WM_TIMER() + ON_WM_DESTROY() + ON_BN_CLICKED(IDC_BUTTON_START, &CElevatorSimulationDlg::OnBnClickedStart) + ON_BN_CLICKED(IDC_BUTTON_PAUSE, &CElevatorSimulationDlg::OnBnClickedPause) + ON_BN_CLICKED(IDC_BUTTON_RESUME, &CElevatorSimulationDlg::OnBnClickedResume) + ON_BN_CLICKED(IDC_BUTTON_RESET, &CElevatorSimulationDlg::OnBnClickedReset) END_MESSAGE_MAP() @@ -99,46 +164,310 @@ BOOL CElevatorSimulationDlg::OnInitDialog() SetIcon(m_hIcon, TRUE); // 设置大图标 SetIcon(m_hIcon, FALSE); // 设置小图标 - SetWindowTextW(L"多电梯群控调度仿真系统 - 核心已就绪"); - if (!m_simulation.Initialize(SimulationConfig{})) + SetWindowTextW(L"多电梯群控调度仿真系统"); + InitializeListControls(); + SetDlgItemTextW(IDC_EDIT_FLOOR_COUNT, L"20"); + SetDlgItemTextW(IDC_EDIT_ELEVATOR_COUNT, L"6"); + SetDlgItemTextW(IDC_EDIT_CAPACITY, L"10"); + SetDlgItemTextW(IDC_EDIT_MOVE_TIME, L"2.0"); + SetDlgItemTextW(IDC_EDIT_PERSON_TIME, L"1.0"); + SetDlgItemTextW(IDC_EDIT_DURATION, L"300"); + SetDlgItemTextW(IDC_EDIT_PASSENGER_RATE, L"0.2"); + SetDlgItemTextW(IDC_EDIT_SPEED, L"1"); + SetDlgItemTextW(IDC_EDIT_SEED, L"42"); + + SimulationConfig config; + config.capacity = 10; + config.personTime = 1.0; + config.simulationDuration = 300.0; + if (!m_simulation.Initialize(config, 42)) { - AfxMessageBox(L"默认参数初始化失败,请检查 Simulation::GetLastError()。", MB_ICONERROR); - SetDlgItemTextW(IDC_SIMULATION_STATUS, L"初始化失败。"); + CString error = L"默认参数初始化失败:" + Utf8ToCString(m_simulation.GetLastError()); + AfxMessageBox(error, MB_ICONERROR); } - else + ResetWallClock(); + if (SetTimer(SimulationTimerId, SimulationTimerIntervalMs, nullptr) == 0) { - RefreshSimulationView(); + AfxMessageBox(L"无法创建 UI 刷新计时器,仿真不会自动推进。", MB_ICONERROR); } - // TODO(E): 后续添加参数输入、开始/暂停/继续/重置按钮与定时刷新。 - // UI 只调用 Simulation 控制接口并显示快照,不自行实现仿真算法。 + RefreshSimulationView(); return TRUE; // 除非将焦点设置到控件,否则返回 TRUE } +void CElevatorSimulationDlg::InitializeListControls() +{ + const DWORD extendedStyle = LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER; + m_elevatorList.SetExtendedStyle(m_elevatorList.GetExtendedStyle() | extendedStyle); + m_floorList.SetExtendedStyle(m_floorList.GetExtendedStyle() | extendedStyle); + m_hallCallList.SetExtendedStyle(m_hallCallList.GetExtendedStyle() | extendedStyle); + + m_elevatorList.InsertColumn(0, L"电梯", LVCFMT_LEFT, 52); + m_elevatorList.InsertColumn(1, L"楼层", LVCFMT_RIGHT, 58); + m_elevatorList.InsertColumn(2, L"方向", LVCFMT_CENTER, 52); + m_elevatorList.InsertColumn(3, L"状态", LVCFMT_LEFT, 92); + m_elevatorList.InsertColumn(4, L"载客", LVCFMT_RIGHT, 72); + + m_floorList.InsertColumn(0, L"楼层", LVCFMT_RIGHT, 62); + m_floorList.InsertColumn(1, L"上行等待", LVCFMT_RIGHT, 82); + m_floorList.InsertColumn(2, L"下行等待", LVCFMT_RIGHT, 82); + + m_hallCallList.InsertColumn(0, L"楼层", LVCFMT_RIGHT, 58); + m_hallCallList.InsertColumn(1, L"方向", LVCFMT_CENTER, 52); + m_hallCallList.InsertColumn(2, L"等待", LVCFMT_RIGHT, 65); + m_hallCallList.InsertColumn(3, L"归属", LVCFMT_LEFT, 88); +} + +void CElevatorSimulationDlg::ResetWallClock() +{ + m_lastWallClock = std::chrono::steady_clock::now(); +} + +bool CElevatorSimulationDlg::ReadIntControl(int controlId, const wchar_t* fieldName, int& value) +{ + CString text; + GetDlgItemTextW(controlId, text); + text.Trim(); + errno = 0; + wchar_t* end = nullptr; + const long parsed = std::wcstol(text.GetString(), &end, 10); + if (text.IsEmpty() || end == text.GetString() || *end != L'\0' || errno == ERANGE || + parsed < (std::numeric_limits::min)() || parsed > (std::numeric_limits::max)()) + { + CString message; + message.Format(L"%s 必须是有效整数。", fieldName); + ShowInputError(message); + return false; + } + value = static_cast(parsed); + return true; +} + +bool CElevatorSimulationDlg::ReadDoubleControl(int controlId, const wchar_t* fieldName, double& value) +{ + CString text; + GetDlgItemTextW(controlId, text); + text.Trim(); + errno = 0; + wchar_t* end = nullptr; + const double parsed = std::wcstod(text.GetString(), &end); + if (text.IsEmpty() || end == text.GetString() || *end != L'\0' || errno == ERANGE) + { + CString message; + message.Format(L"%s 必须是有效数字。", fieldName); + ShowInputError(message); + return false; + } + value = parsed; + return true; +} + +bool CElevatorSimulationDlg::ReadConfiguration(SimulationConfig& config, std::uint32_t& seed) +{ + if (!ReadIntControl(IDC_EDIT_FLOOR_COUNT, L"楼层数 L", config.floorCount) || + !ReadIntControl(IDC_EDIT_ELEVATOR_COUNT, L"电梯数 N", config.elevatorCount) || + !ReadIntControl(IDC_EDIT_CAPACITY, L"容量 K", config.capacity) || + !ReadDoubleControl(IDC_EDIT_MOVE_TIME, L"每层运行时间 S", config.moveTimePerFloor) || + !ReadDoubleControl(IDC_EDIT_PERSON_TIME, L"每人上下客时间 T", config.personTime) || + !ReadDoubleControl(IDC_EDIT_DURATION, L"仿真总时长", config.simulationDuration) || + !ReadDoubleControl(IDC_EDIT_PASSENGER_RATE, L"乘客产生率", config.passengerRate) || + !ReadDoubleControl(IDC_EDIT_SPEED, L"仿真倍速", config.simulationSpeed)) + { + return false; + } + + CString seedText; + GetDlgItemTextW(IDC_EDIT_SEED, seedText); + seedText.Trim(); + errno = 0; + wchar_t* end = nullptr; + const unsigned long long parsedSeed = std::wcstoull(seedText.GetString(), &end, 10); + if (seedText.IsEmpty() || end == seedText.GetString() || *end != L'\0' || errno == ERANGE || + parsedSeed > (std::numeric_limits::max)()) + { + ShowInputError(L"随机种子 seed 必须是 0~4294967295 的整数。"); + return false; + } + seed = static_cast(parsedSeed); + return true; +} + +void CElevatorSimulationDlg::ShowInputError(const CString& message) +{ + SetDlgItemTextW(IDC_SIMULATION_STATE, L"Error / 参数无效"); + AfxMessageBox(message, MB_ICONWARNING); +} + +void CElevatorSimulationDlg::UpdateControlStates() +{ + const SimulationState state = m_simulation.GetState(); + const bool ready = state == SimulationState::Ready || state == SimulationState::Uninitialized; + GetDlgItem(IDC_BUTTON_START)->EnableWindow(ready); + GetDlgItem(IDC_BUTTON_PAUSE)->EnableWindow(state == SimulationState::Running); + GetDlgItem(IDC_BUTTON_RESUME)->EnableWindow(state == SimulationState::Paused); + GetDlgItem(IDC_BUTTON_RESET)->EnableWindow(state != SimulationState::Uninitialized); + + for (int controlId : { IDC_EDIT_FLOOR_COUNT, IDC_EDIT_ELEVATOR_COUNT, IDC_EDIT_CAPACITY, + IDC_EDIT_MOVE_TIME, IDC_EDIT_PERSON_TIME, IDC_EDIT_DURATION, IDC_EDIT_PASSENGER_RATE, + IDC_EDIT_SPEED, IDC_EDIT_SEED }) + { + GetDlgItem(controlId)->EnableWindow(ready); + } +} + void CElevatorSimulationDlg::RefreshSimulationView() { const auto elevators = m_simulation.GetElevatorSnapshots(); const auto floors = m_simulation.GetFloorSnapshots(); const auto statistics = m_simulation.GetStatisticsSnapshot(); - CString text; - text.Format(L"核心已初始化(尚未开始仿真)\r\n" - L"楼层:%zu 电梯:%zu 仿真时间:%.1f 秒\r\n\r\n", - floors.size(), elevators.size(), m_simulation.GetCurrentTime()); - for (const auto& elevator : elevators) - { - CString line; - // 当前视图仅展示初始化快照,初始状态统一为 Idle。 - line.Format(L"E%d %d 层 %d/%d 人 停止\r\n", - elevator.id + 1, elevator.currentFloor, - elevator.passengerCount, elevator.capacity); - text += line; - } - CString footer; - footer.Format(L"\r\n等待:%zu 乘梯:%zu 已到达:%zu\r\n" - L"动态视图、参数输入及控制按钮:待 UI 模块完善。", - statistics.waitingCount, statistics.ridingCount, statistics.arrivedCount); - text += footer; - SetDlgItemTextW(IDC_SIMULATION_STATUS, text); + const auto hallCalls = m_simulation.GetHallCallSnapshots(); + const SimulationConfig config = m_simulation.GetConfig(); + SetDlgItemTextW(IDC_SIMULATION_STATE, SimulationStateText(m_simulation.GetState())); + CString modelTime; + modelTime.Format(L"Model Time: %.1f / %.1f s", m_simulation.GetCurrentTime(), config.simulationDuration); + SetDlgItemTextW(IDC_MODEL_TIME, modelTime); + + if (m_elevatorList.GetItemCount() != static_cast(elevators.size())) + { + m_elevatorList.DeleteAllItems(); + for (std::size_t index = 0; index < elevators.size(); ++index) + m_elevatorList.InsertItem(static_cast(index), L""); + } + for (std::size_t index = 0; index < elevators.size(); ++index) + { + const auto& elevator = elevators[index]; + const int row = static_cast(index); + CString value; + value.Format(L"E%d", elevator.id + 1); + m_elevatorList.SetItemText(row, 0, value); + value.Format(L"%dF", elevator.currentFloor); + m_elevatorList.SetItemText(row, 1, value); + m_elevatorList.SetItemText(row, 2, DirectionText(elevator.direction)); + m_elevatorList.SetItemText(row, 3, ElevatorStateText(elevator.state)); + value.Format(L"%d / %d", elevator.passengerCount, elevator.capacity); + m_elevatorList.SetItemText(row, 4, value); + } + + if (m_floorList.GetItemCount() != static_cast(floors.size())) + { + m_floorList.DeleteAllItems(); + for (std::size_t index = 0; index < floors.size(); ++index) + m_floorList.InsertItem(static_cast(index), L""); + } + for (std::size_t index = 0; index < floors.size(); ++index) + { + const auto& floor = floors[floors.size() - 1 - index]; + const int row = static_cast(index); + CString value; + value.Format(L"%dF", floor.floorNumber); + m_floorList.SetItemText(row, 0, value); + value.Format(L"%zu", floor.upWaitingCount); + m_floorList.SetItemText(row, 1, value); + value.Format(L"%zu", floor.downWaitingCount); + m_floorList.SetItemText(row, 2, value); + } + + m_hallCallList.SetRedraw(FALSE); + m_hallCallList.DeleteAllItems(); + for (std::size_t index = 0; index < hallCalls.size(); ++index) + { + const auto& call = hallCalls[index]; + const int row = static_cast(index); + CString value; + value.Format(L"%dF", call.floorNumber); + m_hallCallList.InsertItem(row, value); + m_hallCallList.SetItemText(row, 1, DirectionText(call.direction)); + value.Format(L"%zu", call.waitingCount); + m_hallCallList.SetItemText(row, 2, value); + if (call.assignedElevatorId == InvalidElevatorId) + value = L"未分配"; + else + value.Format(L"E%d", call.assignedElevatorId + 1); + m_hallCallList.SetItemText(row, 3, value); + } + m_hallCallList.SetRedraw(TRUE); + m_hallCallList.Invalidate(FALSE); + + CString summary; + summary.Format(L"生成: %zu 等待: %zu 乘梯: %zu 到达: %zu " + L"平均等待: %.2f s 平均乘梯: %.2f s 最大等待: %.2f s", + statistics.totalPassengerCount, statistics.waitingCount, statistics.ridingCount, + statistics.arrivedCount, statistics.averageWaitingTime, statistics.averageRideTime, + statistics.maxWaitingTime); + SetDlgItemTextW(IDC_STATISTICS, summary); + UpdateControlStates(); +} + +void CElevatorSimulationDlg::OnBnClickedStart() +{ + SimulationConfig config; + std::uint32_t seed = 0; + if (!ReadConfiguration(config, seed)) return; + if (!m_simulation.Initialize(config, seed)) + { + ShowInputError(L"初始化失败:" + Utf8ToCString(m_simulation.GetLastError())); + return; + } + m_simulation.Start(); + ResetWallClock(); + RefreshSimulationView(); +} + +void CElevatorSimulationDlg::OnBnClickedPause() +{ + m_simulation.Pause(); + ResetWallClock(); + RefreshSimulationView(); +} + +void CElevatorSimulationDlg::OnBnClickedResume() +{ + m_simulation.Resume(); + ResetWallClock(); + RefreshSimulationView(); +} + +void CElevatorSimulationDlg::OnBnClickedReset() +{ + m_simulation.Reset(); + if (m_simulation.GetState() != SimulationState::Ready) + { + // Reset 分配失败时核心保留旧状态;先暂停,避免错误提示停留时间进入下一帧。 + m_simulation.Pause(); + ShowInputError(L"重置失败:" + Utf8ToCString(m_simulation.GetLastError())); + } + ResetWallClock(); + RefreshSimulationView(); +} + +void CElevatorSimulationDlg::OnTimer(UINT_PTR nIDEvent) +{ + if (nIDEvent == SimulationTimerId) + { + const auto now = std::chrono::steady_clock::now(); + const double realDelta = std::chrono::duration(now - m_lastWallClock).count(); + m_lastWallClock = now; + if (m_simulation.IsRunning()) + { + try + { + m_simulation.Update(realDelta); + } + catch (const std::exception& error) + { + m_simulation.Pause(); + AfxMessageBox(L"仿真运行失败:" + Utf8ToCString(error.what()), MB_ICONERROR); + } + } + RefreshSimulationView(); + } + CDialogEx::OnTimer(nIDEvent); +} + +void CElevatorSimulationDlg::OnDestroy() +{ + KillTimer(SimulationTimerId); + CDialogEx::OnDestroy(); } void CElevatorSimulationDlg::OnSysCommand(UINT nID, LPARAM lParam) diff --git a/ElevatorSimulation/ElevatorSimulationDlg.h b/ElevatorSimulation/ElevatorSimulationDlg.h index 8d633ef..e0bd89c 100644 --- a/ElevatorSimulation/ElevatorSimulationDlg.h +++ b/ElevatorSimulation/ElevatorSimulationDlg.h @@ -6,6 +6,8 @@ #include "Core/Simulation.h" +#include + // CElevatorSimulationDlg 对话框 class CElevatorSimulationDlg : public CDialogEx @@ -32,9 +34,30 @@ class CElevatorSimulationDlg : public CDialogEx afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); + afx_msg void OnTimer(UINT_PTR nIDEvent); + afx_msg void OnDestroy(); + afx_msg void OnBnClickedStart(); + afx_msg void OnBnClickedPause(); + afx_msg void OnBnClickedResume(); + afx_msg void OnBnClickedReset(); DECLARE_MESSAGE_MAP() private: + static constexpr UINT_PTR SimulationTimerId = 1; + static constexpr UINT SimulationTimerIntervalMs = 33; + Simulation m_simulation; + CListCtrl m_elevatorList; + CListCtrl m_floorList; + CListCtrl m_hallCallList; + std::chrono::steady_clock::time_point m_lastWallClock; + + void InitializeListControls(); + void ResetWallClock(); + bool ReadConfiguration(SimulationConfig& config, std::uint32_t& seed); + bool ReadIntControl(int controlId, const wchar_t* fieldName, int& value); + bool ReadDoubleControl(int controlId, const wchar_t* fieldName, double& value); + void ShowInputError(const CString& message); + void UpdateControlStates(); void RefreshSimulationView(); }; diff --git a/ElevatorSimulation/Resource.h b/ElevatorSimulation/Resource.h index 813454a..f8e78be 100644 --- a/ElevatorSimulation/Resource.h +++ b/ElevatorSimulation/Resource.h @@ -8,7 +8,25 @@ #define IDS_ABOUTBOX 101 #define IDD_ELEVATORSIMULATION_DIALOG 102 -#define IDC_SIMULATION_STATUS 1000 +#define IDC_EDIT_FLOOR_COUNT 1000 +#define IDC_EDIT_ELEVATOR_COUNT 1001 +#define IDC_EDIT_CAPACITY 1002 +#define IDC_EDIT_MOVE_TIME 1003 +#define IDC_EDIT_PERSON_TIME 1004 +#define IDC_EDIT_DURATION 1005 +#define IDC_EDIT_PASSENGER_RATE 1006 +#define IDC_EDIT_SPEED 1007 +#define IDC_EDIT_SEED 1008 +#define IDC_BUTTON_START 1010 +#define IDC_BUTTON_PAUSE 1011 +#define IDC_BUTTON_RESUME 1012 +#define IDC_BUTTON_RESET 1013 +#define IDC_SIMULATION_STATE 1020 +#define IDC_MODEL_TIME 1021 +#define IDC_LIST_ELEVATORS 1030 +#define IDC_LIST_FLOORS 1031 +#define IDC_LIST_HALL_CALLS 1032 +#define IDC_STATISTICS 1040 // 新对象的下一组默认值 // @@ -16,7 +34,7 @@ #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 129 -#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_CONTROL_VALUE 1041 #define _APS_NEXT_SYMED_VALUE 101 #define _APS_NEXT_COMMAND_VALUE 32771 #endif diff --git a/README.md b/README.md index 5843e32..5e57e23 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ Update 在下一个乘客到达、任意电梯动作完成、当前帧目标时 | B | Core/Elevator.* | 已实现方向保持、顺路停靠、S/T 计时、容量及上下客 | | C | Core/Dispatcher.* | 统一 Cost/ETA 与 LOOK 预演;有限联合分配、滞回改派决策、稳定 tie-break | | D | Core/Simulation.* | 已集成种子、Poisson 到达、手工注入、唯一外呼归属、事件推进和清理 | -| E | ElevatorSimulationDlg.* 与必要资源 | 仍待正式参数输入、开始/暂停/继续/重置、倍速、动态刷新与动画 | +| E | ElevatorSimulationDlg.* 与必要资源 | 已完成初步参数输入、运行控制、倍速、Timer 与 Snapshot 动态列表;正式动画和视觉优化后续再做 | | F | Statistics/*、Tests/* | 已接入事件统计、回归及压力测试;后续扩展算法对照场景和展示 | Dispatcher 无副作用。当前满载梯若预测在请求层接客前释放容量,可以参与候选;若到请求层完成下客后仍满载,则不分配。无合理候选返回 -1。Elevator 只执行已接受任务,不自行寻找整栋楼的乘客。当前方向前方的内呼及双向外呼全部处理至可折返位置后才能反向;仅内呼和同向外呼触发停站服务。满载未上梯乘客继续留队,外呼解除旧归属并重试,不丢弃剩余请求。 @@ -236,6 +236,17 @@ Hall Call 动态改派只由新乘客、到层、上下客完成和零耗时状 历史对照的高客流等待队列旧/新为 1413/1383,乘梯中为 21/14;两版已上梯样本不同,不能只比较均值。90 人场景略有退化。联合分配初版增加了计算量:本机 x64 优化编译、每场景 3 次平均,2000 人约 54→356 ms,高客流约 91→4044 ms(不含编译);不是所有客流都更快或等待更短。完整配置、成本反例、搜索开销及局限见 docs/AlgorithmDesign.md。 +## MFC UI 初步集成 + +当前主对话框已经打通“输入参数 → Start → 实时运行与显示 → Pause/Resume → Finished → Reset”的最小完整链路,界面保持原生 MFC 风格,暂不包含主题、自绘、图片或复杂动画。 + +- 参数区提供楼层数 L、电梯数 N、容量 K、每层运行时间 S、每人上下客时间 T、总时长、全楼乘客产生率、仿真倍速与固定 seed。UI 只做严格的字符串/数值转换,参数范围和 N 为 3 的正倍数等规则仍由 `Simulation::Initialize` 统一校验,失败时显示 `GetLastError()`。 +- 控制区提供开始、暂停、继续和重置。按钮及参数编辑框随 `Ready`、`Running`、`Paused`、`Finished` 状态启用或禁用;Finished 保留最终快照,必须 Reset 后才能开始下一轮。 +- 对话框使用 33 ms MFC Timer 和 `std::chrono::steady_clock` 采样真实经过时间。Timer 仅在 Running 时调用 `Simulation::Update(realDelta)`,不在 UI 再乘 `simulationSpeed`。Start、Pause、Resume、Reset 都会重设墙钟基准,因此暂停期间的真实时间不会在恢复后的第一帧注入核心。 +- 每次刷新只读取 `GetElevatorSnapshots()`、`GetFloorSnapshots()`、`GetHallCallSnapshots()` 和 `GetStatisticsSnapshot()` 的值副本。电梯列表显示 E1~EN、真实楼层、方向、动作状态和载客量;楼层列表按高层到低层显示上下行等待;Hall Call 列表显示等待人数和归属;统计区显示模型时间、生成/等待/乘梯/到达人数及平均等待、平均乘梯、最大等待。 +- Reset 调用核心 `Simulation::Reset()`,沿用最近成功配置和 seed,清空模型时间、乘客、楼层队列和统计并回到 Ready。Ready 状态再次 Start 时会从当前输入框重新 Initialize,因此用户修改参数后不会误用旧配置。 +- 当前 `HallCallSnapshot` 只能表达 assigned/unassigned,不能区分临时 `DeferredCapacity`。界面暂将其显示为“未分配”,灰色 Deferred 展示保留为 TODO;不为此增加第二套容量判断或扩展核心接口。 + ## 使用核心与下一步 ```cpp @@ -253,4 +264,4 @@ if (simulation.Initialize(config, 42)) 测试可设置 passengerRate=0,通过 AddPassenger 注入确定的请求;RunCoreTests.cmd 可指定 Dispatcher、Elevator、Simulation 或 All,第二个参数为 x64/x86。新增测试源在 VS 中作为 Development 文件显示,不加入 MFC 可执行文件,以免产生第二个 main。 -下一步优先由 E 对接现有控制与 Snapshot 接口,完成实际可操作的 MFC 演示。UI 只负责输入、真实时间采样和显示;恢复后重设采样基点,避免把暂停时间算进下一帧。当前无独立开关门时间、加减速、分区停车、峰值预测或全局最优保证;这些是明确的课程设计简化,不是已经实现的高级群控。 +下一步可在不改变 UI/核心边界的前提下完善窗口缩放、视觉样式、Deferred 专门标识和正式动画。当前无独立开关门时间、加减速、分区停车、峰值预测或全局最优保证;这些是明确的课程设计简化,不是已经实现的高级群控。