Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions ElevatorSimulation/Core/Floor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <stdexcept>
#include <algorithm>
#include <unordered_set>

Floor::Floor(int floorNumber) : m_floorNumber(floorNumber)
{
Expand Down Expand Up @@ -45,3 +46,26 @@ const std::deque<PassengerId>& Floor::GetWaitingIds(Direction direction) const
if (direction == Direction::Down) return m_downWaitingPassengers;
throw std::invalid_argument("等待队列方向必须为上行或下行");
}

bool Floor::EnqueueBatch(const std::vector<PassengerId>& ids, Direction direction)
{
if (direction != Direction::Up && direction != Direction::Down) return false;
// 先校验全部 id,通过后再统一入队,保证整批成功或整批失败,不留下部分状态。
std::unordered_set<PassengerId> seen;
seen.reserve(ids.size());
for (PassengerId id : ids)
{
if (id < 0 || Contains(id) || !seen.insert(id).second) return false;
}
auto& queue = direction == Direction::Up ? m_upWaitingPassengers : m_downWaitingPassengers;
queue.insert(queue.end(), ids.begin(), ids.end());
return true;
}

bool Floor::Contains(PassengerId id) const noexcept
{
if (id < 0) return false;
for (const auto* queue : { &m_upWaitingPassengers, &m_downWaitingPassengers })
if (std::find(queue->begin(), queue->end(), id) != queue->end()) return true;
return false;
}
7 changes: 7 additions & 0 deletions ElevatorSimulation/Core/Floor.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "CommonTypes.h"

#include <deque>
#include <vector>

class Floor
{
Expand All @@ -19,6 +20,12 @@ class Floor
PassengerId Peek(Direction direction) const;
const std::deque<PassengerId>& GetWaitingIds(Direction direction) const;

// 批量交通输入:一次性把同一方向的整组乘客按 FIFO 顺序入队,供批量/分组到达场景使用。
// 任一项 id 非法、批内重复或已在本层等待时整批失败,不做部分入队。
bool EnqueueBatch(const std::vector<PassengerId>& ids, Direction direction);
// 查询指定乘客是否正等待在本层任一方向队列中,用于去重与一致性校验。
bool Contains(PassengerId id) const noexcept;

private:
int m_floorNumber;
std::deque<PassengerId> m_upWaitingPassengers;
Expand Down
4 changes: 4 additions & 0 deletions ElevatorSimulation/ElevatorSimulation.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,10 @@
<None Include="..\Tests\FleetRebalancerTests.cpp" />
<None Include="..\Tests\ElevatorTests.cpp" />
<None Include="..\Tests\SimulationTests.cpp" />
<None Include="..\Tests\FloorTests.cpp" />
<None Include="..\Tests\StatisticsTests.cpp" />
<None Include="..\Tests\SystemTests.cpp" />
<None Include="..\Tests\ReliabilityTests.cpp" />
<None Include="..\Tests\ConcurrencyTests.cpp" />
<None Include="..\Tests\DispatchPerformance.cpp" />
<None Include="..\Tests\RunDispatchPerformance.ps1" />
Expand Down
12 changes: 12 additions & 0 deletions ElevatorSimulation/ElevatorSimulation.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,18 @@
<None Include="..\Tests\SimulationTests.cpp">
<Filter>Development</Filter>
</None>
<None Include="..\Tests\FloorTests.cpp">
<Filter>Development</Filter>
</None>
<None Include="..\Tests\StatisticsTests.cpp">
<Filter>Development</Filter>
</None>
<None Include="..\Tests\SystemTests.cpp">
<Filter>Development</Filter>
</None>
<None Include="..\Tests\ReliabilityTests.cpp">
<Filter>Development</Filter>
</None>
<None Include="..\Tests\ConcurrencyTests.cpp">
<Filter>Development</Filter>
</None>
Expand Down
60 changes: 58 additions & 2 deletions ElevatorSimulation/Statistics/Statistics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <utility>
#include <algorithm>
#include <cmath>
#include <sstream>
#include <iomanip>

void Statistics::Reset(int elevatorCount, int floorCount)
{
Expand All @@ -20,6 +22,10 @@ void Statistics::Reset(int elevatorCount, int floorCount)
m_snapshot = std::move(snapshot);
m_waitingTimeSum = 0.0;
m_rideTimeSum = 0.0;
const std::size_t count = static_cast<std::size_t>(elevatorCount);
m_fullLoadCounts.assign(count, std::size_t{ 0 });
m_wasFull.assign(count, false);
m_workingTimes.assign(count, 0.0);
}

StatisticsSnapshot Statistics::GetSnapshot() const
Expand Down Expand Up @@ -83,7 +89,57 @@ void Statistics::ElevatorMoved(int elevatorId, bool empty)
void Statistics::ElevatorTimeElapsed(int elevatorId, double seconds, ElevatorState state, bool full)
{
if (!std::isfinite(seconds) || seconds < 0.0) throw std::invalid_argument("经过时间无效");
auto& elevator = m_snapshot.elevators.at(static_cast<std::size_t>(elevatorId));
const std::size_t index = static_cast<std::size_t>(elevatorId);
auto& elevator = m_snapshot.elevators.at(index);
if (state == ElevatorState::Idle) elevator.idleTime += seconds;
if (full) elevator.fullTime += seconds;
else m_workingTimes.at(index) += seconds; // 工作时间:非空闲状态累计。
if (full)
{
elevator.fullTime += seconds;
// 满载次数按连续满载时段计数,避免同一时段逐帧重复累计。
if (!m_wasFull.at(index))
{
++m_fullLoadCounts.at(index);
m_wasFull.at(index) = true;
}
}
else
m_wasFull.at(index) = false;
}

std::size_t Statistics::GetFullLoadCount(int elevatorId) const
{
return m_fullLoadCounts.at(static_cast<std::size_t>(elevatorId));
}

double Statistics::GetWorkingTime(int elevatorId) const
{
return m_workingTimes.at(static_cast<std::size_t>(elevatorId));
}

std::string Statistics::FormatSummary() const
{
std::ostringstream output;
output << std::fixed << std::setprecision(2);
output << "总乘客=" << m_snapshot.totalPassengerCount
<< " 等待中=" << m_snapshot.waitingCount
<< " 乘梯中=" << m_snapshot.ridingCount
<< " 已到达=" << m_snapshot.arrivedCount
<< " 平均等待=" << m_snapshot.averageWaitingTime << "s"
<< " 最大等待=" << m_snapshot.maxWaitingTime << "s"
<< " 平均乘梯=" << m_snapshot.averageRideTime << "s\n";
for (const auto& elevator : m_snapshot.elevators)
{
// UI 显示 E1~EN,即 id + 1。
const std::size_t index = static_cast<std::size_t>(elevator.id);
output << "E" << elevator.id + 1
<< ": 送达=" << elevator.transportedCount
<< " 移动=" << elevator.traveledFloors
<< " 空驶=" << elevator.emptyTravelFloors
<< " 满载=" << m_fullLoadCounts.at(index) << "次"
<< " 满载时长=" << elevator.fullTime << "s"
<< " 空闲=" << elevator.idleTime << "s"
<< " 工作=" << m_workingTimes.at(index) << "s\n";
}
return output.str();
}
12 changes: 12 additions & 0 deletions ElevatorSimulation/Statistics/Statistics.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

#include "../Core/CommonTypes.h"

#include <string>
#include <vector>

// 被 Simulation 组合,仅依赖 Common,不反向依赖 Simulation/UI。
class Statistics
{
Expand All @@ -15,8 +18,17 @@ class Statistics
void ElevatorMoved(int elevatorId, bool empty);
void ElevatorTimeElapsed(int elevatorId, double seconds, ElevatorState state, bool full);

// 只读文本汇总,供测试与后续 UI 展示;不改变统计状态。
std::string FormatSummary() const;
// 单梯满载次数(连续满载时段计数)与工作时间(非空闲累计秒);越界抛 out_of_range。
std::size_t GetFullLoadCount(int elevatorId) const;
double GetWorkingTime(int elevatorId) const;

private:
StatisticsSnapshot m_snapshot;
double m_waitingTimeSum = 0.0;
double m_rideTimeSum = 0.0;
std::vector<std::size_t> m_fullLoadCounts;
std::vector<bool> m_wasFull;
std::vector<double> m_workingTimes;
};
45 changes: 43 additions & 2 deletions Tests/DispatchComparison.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ namespace
config.moveTimePerFloor=0.3; config.personTime=0.2;
config.passengerRate=8; config.simulationDuration=600;
}
if(name=="evening_peak")
{
config.elevatorCount=6; config.capacity=4;
config.moveTimePerFloor=0.3; config.personTime=0.2; config.simulationDuration=600;
}
if(name=="interfloor" || name=="saturation")
{
config.elevatorCount=6; config.capacity=name=="saturation" ? 2 : 4;
config.moveTimePerFloor=0.3; config.personTime=0.2; config.simulationDuration=20000;
}
Simulation simulation;
if(!simulation.Initialize(config,321)) throw std::runtime_error("initialize failed");
if(name=="two_calls")
Expand All @@ -40,6 +50,35 @@ namespace
simulation.AddPassenger(start,(start-1+1+i%19)%20+1);
}
}
else if(name=="evening_peak")
{
// 晚高峰下行:偶数乘客从上层→1,奇数乘客从 1→上层。
for(int i=0;i<300;++i)
{
const int floor=2+i%19;
if(i%2==0) simulation.AddPassenger(floor,1);
else simulation.AddPassenger(1,floor);
}
}
else if(name=="interfloor")
{
// 区间双向:在 5~16 层内错位起终点,均衡上行/下行。
for(int i=0;i<300;++i)
{
const int start=5+i%12;
const int target=(start-4+i%11)%12+5;
simulation.AddPassenger(start,target);
}
}
else if(name=="saturation")
{
// 容量饱和:capacity=2,600 人有限批次,考查高积压下的送达与等待。
for(int i=0;i<600;++i)
{
const int start=i%20+1;
simulation.AddPassenger(start,(start-1+1+i%19)%20+1);
}
}
simulation.Start();
if(name=="new_detour")
{
Expand All @@ -56,14 +95,16 @@ int main()
try
{
std::cout << "scenario,total,arrived,waiting,riding,mean_wait_s,completed_pickup_delay_s,elapsed_ms\n";
for(const std::string name:{"two_calls","new_detour","finite90","batch2000","poisson321"})
for(const std::string name:{"two_calls","new_detour","finite90","batch2000","poisson321",
"evening_peak","interfloor","saturation"})
{
StatisticsSnapshot stats;
const auto begin=std::chrono::steady_clock::now();
constexpr int repeats=3;
for(int run=0;run<repeats;++run) stats=RunScenario(name);
const auto elapsed=std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-begin).count()/repeats;
const double personTime=name=="finite90" || name=="batch2000" ? 0.25 : (name=="poisson321" ? 0.2 : 3.0);
const double personTime=name=="finite90" || name=="batch2000" ? 0.25 :
(name=="poisson321" || name=="evening_peak" || name=="interfloor" || name=="saturation" ? 0.2 : 3.0);
// 已完成上梯者的响应延迟总和,扣掉其自身上梯 T;积压另列,不能当作全体均值。
const double response=(stats.averageWaitingTime-personTime)*stats.boardedCount;
std::cout << std::fixed << std::setprecision(4) << name << ',' << stats.totalPassengerCount << ','
Expand Down
81 changes: 81 additions & 0 deletions Tests/FloorTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include "Core/Floor.h"
#include "TestSupport.h"

#include <vector>

// A 部分(Floor)批量交通输入接口回归:EnqueueBatch 原子整组入队与 Contains 去重/一致性校验。
int main()
{
TestSuite tests("Floor");
tests.Run("initial state empty", [&] {
Floor floor(1);
tests.Check(floor.GetUpWaitingCount() == 0 && floor.GetDownWaitingCount() == 0, "empty queues");
tests.Check(!floor.Contains(0) && !floor.Contains(-1), "contains empty");
});
tests.Run("batch enqueue FIFO order", [&] {
Floor floor(3);
tests.Check(floor.EnqueueBatch({ 0, 1, 2 }, Direction::Up), "batch accepted");
tests.Check(floor.GetUpWaitingCount() == 3 && floor.GetDownWaitingCount() == 0, "up count");
const auto& up = floor.GetWaitingIds(Direction::Up);
tests.Check(up.size() == 3 && up[0] == 0 && up[1] == 1 && up[2] == 2, "order preserved");
tests.Check(floor.Peek(Direction::Up) == 0, "peek head");
tests.Check(floor.Peek(Direction::Down) == InvalidPassengerId, "empty down peek");
});
tests.Run("directions are independent queues", [&] {
Floor floor(3);
tests.Check(floor.EnqueueBatch({ 0, 1 }, Direction::Up), "up batch");
tests.Check(floor.EnqueueBatch({ 2, 3 }, Direction::Down), "down batch");
tests.Check(floor.GetUpWaitingCount() == 2 && floor.GetDownWaitingCount() == 2, "split counts");
tests.Check(floor.GetWaitingIds(Direction::Up)[0] == 0, "up queue head");
tests.Check(floor.GetWaitingIds(Direction::Down)[0] == 2, "down queue head");
});
tests.Run("batch duplicate rejected atomically", [&] {
Floor floor(1);
tests.Check(!floor.EnqueueBatch({ 4, 4 }, Direction::Up), "in-batch duplicate");
tests.Check(floor.GetUpWaitingCount() == 0, "no partial enqueue");
});
tests.Run("already waiting id rejects batch", [&] {
Floor floor(1);
tests.Check(floor.Enqueue(5, Direction::Up), "seed single");
tests.Check(!floor.EnqueueBatch({ 6, 5 }, Direction::Up), "waiting id rejects");
tests.Check(floor.GetUpWaitingCount() == 1, "no partial enqueue");
// 跨方向去重口径与单条 Enqueue 一致:同一 id 不能同时出现在上下行。
tests.Check(!floor.EnqueueBatch({ 5 }, Direction::Down), "cross-direction duplicate");
tests.Check(floor.GetDownWaitingCount() == 0, "down still empty");
});
tests.Run("invalid parameters rejected", [&] {
Floor floor(1);
tests.Check(!floor.EnqueueBatch({ 7, -1 }, Direction::Up), "negative id");
tests.Check(!floor.EnqueueBatch({ 7 }, Direction::Idle), "idle direction");
tests.Check(floor.GetUpWaitingCount() == 0, "no partial enqueue");
});
tests.Run("empty batch is harmless no-op", [&] {
Floor floor(1);
tests.Check(floor.EnqueueBatch({}, Direction::Up), "empty accepted");
tests.Check(floor.GetUpWaitingCount() == 0, "unchanged");
});
tests.Run("append preserves existing FIFO", [&] {
Floor floor(1);
tests.Check(floor.EnqueueBatch({ 0, 1 }, Direction::Up), "first batch");
tests.Check(floor.EnqueueBatch({ 2, 3 }, Direction::Up), "second batch");
const auto& up = floor.GetWaitingIds(Direction::Up);
tests.Check(up.size() == 4 && up[0] == 0 && up[1] == 1 && up[2] == 2 && up[3] == 3, "global order");
});
tests.Run("contains tracks removal", [&] {
Floor floor(1);
tests.Check(floor.EnqueueBatch({ 0, 1, 2 }, Direction::Up), "seed batch");
tests.Check(floor.Contains(0) && floor.Contains(1) && floor.Contains(2), "all contained");
tests.Check(!floor.Contains(3) && !floor.Contains(-1), "absent ids");
tests.Check(floor.RemoveFront(0, Direction::Up), "remove head");
tests.Check(!floor.Contains(0), "removed id gone");
tests.Check(floor.Contains(1), "remaining kept");
});
tests.Run("snapshot reflects batch counts", [&] {
Floor floor(3);
tests.Check(floor.EnqueueBatch({ 0, 1 }, Direction::Up) && floor.EnqueueBatch({ 2 }, Direction::Down), "seed batches");
const auto snapshot = floor.GetSnapshot();
tests.Check(snapshot.floorNumber == 3 && snapshot.upWaitingCount == 2 && snapshot.downWaitingCount == 1,
"snapshot counts");
});
return tests.Finish();
}
Loading