#pragma once // Object states are captured while an edit is in progress, then reduced to the // changed objects. Ordering is only retained for insertions, deletions or reordering. template struct lcObjectHistory { std::vector States; std::optional> Order; template void Save(const std::vector>& Objects) { States.clear(); States.reserve(Objects.size()); Order.emplace(); Order->reserve(Objects.size()); for (const std::unique_ptr& Object : Objects) { States.emplace_back(Object->GetHistoryState()); Order->emplace_back(static_cast(Object->GetId())); } } bool IsEmpty() const { return States.empty() && !Order; } static void Compact(lcObjectHistory& Start, lcObjectHistory& End) { std::unordered_map StartStates; std::unordered_map EndStates; StartStates.reserve(Start.States.size()); EndStates.reserve(End.States.size()); for (const StateType& State : Start.States) StartStates.emplace(static_cast(State.Id), &State); for (const StateType& State : End.States) EndStates.emplace(static_cast(State.Id), &State); std::unordered_set Changed; for (const StateType& State : Start.States) { const auto Other = EndStates.find(static_cast(State.Id)); if (Other == EndStates.end() || !(State == *Other->second)) Changed.emplace(static_cast(State.Id)); } for (const StateType& State : End.States) if (StartStates.find(static_cast(State.Id)) == StartStates.end()) Changed.emplace(static_cast(State.Id)); auto KeepChanges = [&Changed](std::vector& States) { std::vector Changes; Changes.reserve(std::min(States.size(), Changed.size())); for (StateType& State : States) if (Changed.find(static_cast(State.Id)) != Changed.end()) Changes.emplace_back(std::move(State)); States = std::move(Changes); }; KeepChanges(Start.States); KeepChanges(End.States); if (Start.Order == End.Order) { Start.Order.reset(); End.Order.reset(); } } static void Merge(lcObjectHistory& Start, lcObjectHistory& End, lcObjectHistory& NextStart, lcObjectHistory& NextEnd) { std::map Before; std::map After; for (StateType& State : Start.States) Before.emplace(static_cast(State.Id), std::move(State)); for (StateType& State : End.States) After.emplace(static_cast(State.Id), std::move(State)); for (StateType& State : NextStart.States) { const uint32_t Id = static_cast(State.Id); if (After.find(Id) == After.end()) Before.emplace(Id, std::move(State)); } for (const StateType& State : NextStart.States) After.erase(static_cast(State.Id)); for (StateType& State : NextEnd.States) After.insert_or_assign(static_cast(State.Id), std::move(State)); Start.States.clear(); End.States.clear(); for (auto& [Id, State] : Before) Start.States.emplace_back(std::move(State)); for (auto& [Id, State] : After) End.States.emplace_back(std::move(State)); if (!Start.Order) Start.Order = std::move(NextStart.Order); if (NextEnd.Order) End.Order = std::move(NextEnd.Order); Compact(Start, End); } template std::unordered_map RestoreObjects(std::vector>& Objects) const { std::unordered_map ObjectsById; if (!Order) { ObjectsById.reserve(Objects.size()); for (const std::unique_ptr& Object : Objects) ObjectsById.emplace(static_cast(Object->GetId()), Object.get()); return ObjectsById; } std::unordered_map ObjectIndices; ObjectIndices.reserve(Objects.size()); for (size_t Index = 0; Index < Objects.size(); Index++) ObjectIndices.emplace(static_cast(Objects[Index]->GetId()), Index); std::unordered_set StateIds; std::vector> NewObjects; for (const StateType& State : States) StateIds.emplace(static_cast(State.Id)); NewObjects.reserve(Order->size()); ObjectsById.reserve(Order->size()); for (uint32_t Id : *Order) { const auto Object = ObjectIndices.find(Id); if (Object != ObjectIndices.end()) NewObjects.emplace_back(std::move(Objects[Object->second])); else if (StateIds.find(Id) != StateIds.end()) NewObjects.emplace_back(std::make_unique()); else continue; // A non-undoable model-list change may have removed it. // New objects do not have their saved IDs until their state is restored. ObjectsById.emplace(Id, NewObjects.back().get()); } Objects = std::move(NewObjects); return ObjectsById; } };