//////////////////////////////////////////////////////////////////////
// This file is part of Remere's Map Editor
//////////////////////////////////////////////////////////////////////
// Remere's Map Editor is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Remere's Map Editor is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//////////////////////////////////////////////////////////////////////
#include "main.h"
#include "action.h"
#include "settings.h"
#include "map.h"
#include "editor.h"
#include "gui.h"
// Add necessary includes for exception handling and file operations
#include
#include
#include
#include
#include
Change::Change() :
type(CHANGE_NONE), data(nullptr) {
////
}
Change::Change(Tile* t) :
type(CHANGE_TILE) {
ASSERT(t);
data = t;
}
Change* Change::Create(House* house, const Position& where) {
Change* c = newd Change();
c->type = CHANGE_MOVE_HOUSE_EXIT;
std::pair* p = newd std::pair;
p->first = house->getID();
p->second = where;
c->data = p;
return c;
}
Change* Change::Create(Waypoint* wp, const Position& where) {
Change* c = newd Change();
c->type = CHANGE_MOVE_WAYPOINT;
std::pair* p = newd std::pair;
p->first = wp->name;
p->second = where;
c->data = p;
return c;
}
Change::~Change() {
clear();
}
void Change::clear() {
switch (type) {
case CHANGE_TILE:
ASSERT(data);
delete reinterpret_cast(data);
break;
case CHANGE_MOVE_HOUSE_EXIT:
ASSERT(data);
delete reinterpret_cast*>(data);
break;
case CHANGE_MOVE_WAYPOINT:
ASSERT(data);
delete reinterpret_cast*>(data);
break;
case CHANGE_NONE:
break;
default:
#ifdef __DEBUG_MODE__
if (data) {
printf("UNHANDLED CHANGE TYPE! Leak!");
}
#endif
break;
}
type = CHANGE_NONE;
data = nullptr;
}
uint32_t Change::memsize() const {
uint32_t mem = sizeof(*this);
switch (type) {
case CHANGE_TILE:
ASSERT(data);
mem += reinterpret_cast(data)->memsize();
break;
default:
break;
}
return mem;
}
Action::Action(Editor& editor, ActionIdentifier ident) :
commited(false),
editor(editor),
type(ident) {
}
Action::~Action() {
ChangeList::const_reverse_iterator it = changes.rbegin();
while (it != changes.rend()) {
delete *it;
++it;
}
}
size_t Action::approx_memsize() const {
uint32_t mem = sizeof(*this);
mem += changes.size() * (sizeof(Change) + sizeof(Tile) + sizeof(Item) + 6 /* approx overhead*/);
return mem;
}
size_t Action::memsize() const {
uint32_t mem = sizeof(*this);
mem += sizeof(Change*) * 3 * changes.size();
ChangeList::const_iterator it = changes.begin();
while (it != changes.end()) {
Change* c = *it;
switch (c->type) {
case CHANGE_TILE: {
ASSERT(c->data);
mem += reinterpret_cast(c->data)->memsize();
break;
}
default:
break;
}
++it;
}
return mem;
}
void Action::commit(DirtyList* dirty_list) {
editor.selection.start(Selection::INTERNAL);
ChangeList::const_iterator it = changes.begin();
while (it != changes.end()) {
Change* c = *it;
switch (c->type) {
case CHANGE_TILE: {
void** data = &c->data;
Tile* newtile = reinterpret_cast(*data);
ASSERT(newtile);
Position pos = newtile->getPosition();
if (editor.IsLiveClient()) {
QTreeNode* nd = editor.map.getLeaf(pos.x, pos.y);
if (!nd || !nd->isVisible(pos.z > GROUND_LAYER)) {
// Delete all changes that affect tiles outside our view
c->clear();
++it;
continue;
}
}
Tile* oldtile = editor.map.swapTile(pos, newtile);
TileLocation* location = newtile->getLocation();
// Update other nodes in the network
if (editor.IsLiveServer() && dirty_list) {
dirty_list->AddPosition(pos.x, pos.y, pos.z);
}
newtile->update();
// std::cout << "\tSwitched tile at " << pos.x << ";" << pos.y << ";" << pos.z << " from " << (void*)oldtile << " to " << *data << std::endl;
if (newtile->isSelected()) {
editor.selection.addInternal(newtile);
}
if (oldtile) {
if (newtile->getHouseID() != oldtile->getHouseID()) {
// oooooomggzzz we need to add it to the appropriate house!
House* house = editor.map.houses.getHouse(oldtile->getHouseID());
if (house) {
house->removeTile(oldtile);
}
house = editor.map.houses.getHouse(newtile->getHouseID());
if (house) {
house->addTile(newtile);
}
}
if (oldtile->spawn) {
if (newtile->spawn) {
if (*oldtile->spawn != *newtile->spawn) {
editor.map.removeSpawn(oldtile);
editor.map.addSpawn(newtile);
}
} else {
// Spawn has been removed
editor.map.removeSpawn(oldtile);
}
} else if (newtile->spawn) {
editor.map.addSpawn(newtile);
}
// oldtile->update();
if (oldtile->isSelected()) {
editor.selection.removeInternal(oldtile);
}
*data = oldtile;
} else {
*data = editor.map.allocator(location);
if (newtile->getHouseID() != 0) {
// oooooomggzzz we need to add it to the appropriate house!
House* house = editor.map.houses.getHouse(newtile->getHouseID());
if (house) {
house->addTile(newtile);
}
}
if (newtile->spawn) {
editor.map.addSpawn(newtile);
}
}
// Mark the tile as modified
newtile->modify();
// Update client dirty list
if (editor.IsLiveClient() && dirty_list && type != ACTION_REMOTE) {
// Local action, assemble changes
dirty_list->AddChange(c);
}
break;
}
case CHANGE_MOVE_HOUSE_EXIT: {
std::pair* p = reinterpret_cast*>(c->data);
ASSERT(p);
House* whathouse = editor.map.houses.getHouse(p->first);
if (whathouse) {
Position oldpos = whathouse->getExit();
whathouse->setExit(p->second);
p->second = oldpos;
}
break;
}
case CHANGE_MOVE_WAYPOINT: {
std::pair* p = reinterpret_cast*>(c->data);
ASSERT(p);
Waypoint* wp = editor.map.waypoints.getWaypoint(p->first);
if (wp) {
// Change the tiles
TileLocation* oldtile = editor.map.getTileL(wp->pos);
TileLocation* newtile = editor.map.getTileL(p->second);
// Only need to remove from old if it actually exists
if (p->second != Position()) {
if (oldtile && oldtile->getWaypointCount() > 0) {
oldtile->decreaseWaypointCount();
}
}
newtile->increaseWaypointCount();
// Update shit
Position oldpos = wp->pos;
wp->pos = p->second;
p->second = oldpos;
}
break;
}
default:
break;
}
++it;
}
editor.selection.finish(Selection::INTERNAL);
commited = true;
}
void Action::undo(DirtyList* dirty_list) {
if (changes.empty()) {
return;
}
editor.selection.start(Selection::INTERNAL);
ChangeList::reverse_iterator it = changes.rbegin();
while (it != changes.rend()) {
Change* c = *it;
switch (c->type) {
case CHANGE_TILE: {
void** data = &c->data;
Tile* oldtile = reinterpret_cast(*data);
ASSERT(oldtile);
Position pos = oldtile->getPosition();
if (editor.IsLiveClient()) {
QTreeNode* nd = editor.map.getLeaf(pos.x, pos.y);
if (!nd || !nd->isVisible(pos.z > GROUND_LAYER)) {
// Delete all changes that affect tiles outside our view
c->clear();
++it;
continue;
}
}
Tile* newtile = editor.map.swapTile(pos, oldtile);
// Update server side change list (for broadcast)
if (editor.IsLiveServer() && dirty_list) {
dirty_list->AddPosition(pos.x, pos.y, pos.z);
}
if (oldtile->isSelected()) {
editor.selection.addInternal(oldtile);
}
if (newtile->isSelected()) {
editor.selection.removeInternal(newtile);
}
if (newtile->getHouseID() != oldtile->getHouseID()) {
// oooooomggzzz we need to remove it from the appropriate house!
House* house = editor.map.houses.getHouse(newtile->getHouseID());
if (house) {
house->removeTile(newtile);
} else {
// Set tile house to 0, house has been removed
newtile->setHouse(nullptr);
}
house = editor.map.houses.getHouse(oldtile->getHouseID());
if (house) {
house->addTile(oldtile);
}
}
if (oldtile->spawn) {
if (newtile->spawn) {
if (*oldtile->spawn != *newtile->spawn) {
editor.map.removeSpawn(newtile);
editor.map.addSpawn(oldtile);
}
} else {
editor.map.addSpawn(oldtile);
}
} else if (newtile->spawn) {
editor.map.removeSpawn(newtile);
}
*data = newtile;
// Update client dirty list
if (editor.IsLiveClient() && dirty_list && type != ACTION_REMOTE) {
// Local action, assemble changes
dirty_list->AddChange(c);
}
break;
}
case CHANGE_MOVE_HOUSE_EXIT: {
std::pair* p = reinterpret_cast*>(c->data);
ASSERT(p);
House* whathouse = editor.map.houses.getHouse(p->first);
if (whathouse) {
Position oldpos = whathouse->getExit();
whathouse->setExit(p->second);
p->second = oldpos;
}
break;
}
case CHANGE_MOVE_WAYPOINT: {
std::pair* p = reinterpret_cast*>(c->data);
ASSERT(p);
Waypoint* wp = editor.map.waypoints.getWaypoint(p->first);
if (wp) {
// Change the tiles
TileLocation* oldtile = editor.map.getTileL(wp->pos);
TileLocation* newtile = editor.map.getTileL(p->second);
// Only need to remove from old if it actually exists
if (p->second != Position()) {
if (oldtile && oldtile->getWaypointCount() > 0) {
oldtile->decreaseWaypointCount();
}
}
if (newtile) {
newtile->increaseWaypointCount();
}
// Update shit
Position oldpos = wp->pos;
wp->pos = p->second;
p->second = oldpos;
}
break;
}
default:
break;
}
++it;
}
editor.selection.finish(Selection::INTERNAL);
commited = false;
}
BatchAction::BatchAction(Editor& editor, ActionIdentifier ident) :
editor(editor),
timestamp(0),
memory_size(0),
type(ident) {
////
}
BatchAction::~BatchAction() {
try {
// Make a copy of the batch to avoid modifying while iterating
ActionVector batchCopy;
batchCopy.swap(batch); // Swap is safer than copying
// Now safely delete each action in the copy
for (ActionVector::iterator it = batchCopy.begin(); it != batchCopy.end(); ++it) {
try {
Action* action = *it;
if (action) {
delete action;
}
} catch (const std::exception& e) {
// Log error but continue cleanup
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Error in BatchAction destructor: " << e.what() << "\n";
logFile.close();
}
}
}
// Ensure batch is empty (should already be from the swap)
batch.clear();
} catch (const std::exception& e) {
// Log error but don't crash
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Critical error in BatchAction destructor: " << e.what() << "\n";
logFile.close();
}
}
}
size_t BatchAction::memsize(bool recalc) const {
// Expensive operation, only evaluate once (won't change anyways)
if (!recalc && memory_size > 0) {
return memory_size;
}
uint32_t mem = sizeof(*this);
mem += sizeof(Action*) * 3 * batch.size();
for (Action* action : batch) {
#ifdef __USE_EXACT_MEMSIZE__
mem += action->memsize();
#else
// Less exact but MUCH faster
mem += action->approx_memsize();
#endif
}
const_cast(this)->memory_size = mem;
return mem;
}
void BatchAction::addAction(Action* action) {
try {
// Safety check for null action
if (!action) {
return;
}
// If empty, do nothing.
if (action->size() == 0) {
delete action;
return;
}
ASSERT(action->getType() == type);
if (!editor.CanEdit()) {
delete action;
return;
}
// Add it!
batch.push_back(action);
timestamp = time(nullptr);
}
catch (const std::exception& e) {
// Log error but don't crash
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Error in BatchAction::addAction: " << e.what() << "\n";
logFile.close();
}
// Try to clean up action if possible
try {
delete action;
}
catch (...) {
// Ignore cleanup errors
}
}
}
void BatchAction::addAndCommitAction(Action* action) {
// If empty, do nothing.
if (action->size() == 0) {
delete action;
return;
}
if (!editor.CanEdit()) {
delete action;
return;
}
// Add it!
action->commit(nullptr);
batch.push_back(action);
timestamp = time(nullptr);
}
void BatchAction::commit() {
for (Action* action : batch) {
if (!action->isCommited()) {
action->commit(nullptr);
}
}
}
void BatchAction::undo() {
for (Action* action : boost::adaptors::reverse(batch)) {
action->undo(nullptr);
}
}
void BatchAction::redo() {
for (Action* action : batch) {
action->redo(nullptr);
}
}
void BatchAction::merge(BatchAction* other) {
try {
// Safety check for null pointer
if (!other) {
return;
}
// Reserve space to avoid multiple reallocations
batch.reserve(batch.size() + other->batch.size());
// Move actions from other batch to this one
for (ActionVector::iterator it = other->batch.begin(); it != other->batch.end(); /* no increment here */) {
Action* action = *it;
it = other->batch.erase(it); // Remove from source vector before adding to destination
if (action) {
batch.push_back(action);
}
}
// Clear the other batch's vector (should already be empty)
other->batch.clear();
} catch (const std::exception& e) {
// Log error but don't crash
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Error merging batch actions: " << e.what() << "\n";
logFile.close();
}
}
}
ActionQueue::ActionQueue(Editor& editor) :
current(0), memory_size(0), editor(editor) {
////
}
ActionQueue::~ActionQueue() {
try {
for (auto it = actions.begin(); it != actions.end(); /* no increment here */) {
BatchAction* action = *it;
it = actions.erase(it); // Remove from container before deleting
try {
delete action;
} catch (const std::exception& e) {
// Log error but continue cleanup
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Error deleting action in queue destructor: " << e.what() << "\n";
logFile.close();
}
}
}
actions.clear(); // Ensure container is empty
} catch (const std::exception& e) {
// Log error but don't crash
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Error in ActionQueue destructor: " << e.what() << "\n";
logFile.close();
}
}
}
Action* ActionQueue::createAction(ActionIdentifier ident) {
return newd Action(editor, ident);
}
Action* ActionQueue::createAction(BatchAction* batch) {
return newd Action(editor, batch->getType());
}
BatchAction* ActionQueue::createBatch(ActionIdentifier ident) {
return newd BatchAction(editor, ident);
}
void ActionQueue::resetTimer() {
if (!actions.empty()) {
actions.back()->resetTimer();
}
}
void ActionQueue::addAction(Action* action, int stacking_delay) {
// Ensure we're on the main thread
if (!wxThread::IsMain()) {
// Use CallAfter with a copy of parameters to prevent race conditions
Action* actionPtr = action;
int delay = stacking_delay;
wxTheApp->CallAfter([this, actionPtr, delay]() {
this->addAction(actionPtr, delay);
});
return;
}
// Safety check for null action
if (!action) {
return;
}
try {
// If empty, do nothing.
if (action->size() == 0) {
delete action;
return;
}
BatchAction* batch = createBatch(action->getType());
if (!batch) {
delete action;
return;
}
try {
batch->addAndCommitAction(action);
// If batch is empty after adding action, clean up and return
if (batch->size() == 0) {
delete batch;
return;
}
addBatch(batch, stacking_delay);
}
catch (const std::exception& e) {
// Log error but don't crash
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Error in addAction: " << e.what() << "\n";
logFile.close();
}
// Clean up if exception occurred
delete batch;
}
}
catch (const std::exception& e) {
// Log error but don't crash
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Critical error in addAction: " << e.what() << "\n";
logFile.close();
}
// Try to clean up action if possible
try {
delete action;
}
catch (...) {
// Ignore cleanup errors
}
}
}
void ActionQueue::addBatch(BatchAction* batch, int stacking_delay) {
// Ensure we're on the main thread
if (!wxThread::IsMain()) {
// Use CallAfter but with a 'copy' of needed parameters
// to prevent race conditions
ActionQueue* self = this;
int delay = stacking_delay;
wxTheApp->CallAfter([self, batch, delay]() {
self->addBatch(batch, delay);
});
return;
}
// Safety check - if batch is null, just return
if (!batch) {
return;
}
// Additional safety check to catch any potential crashes
try {
ASSERT(current <= actions.size());
if (batch->size() == 0) {
delete batch;
return;
}
// Commit any uncommited actions...
batch->commit();
// Update title
if (editor.map.doChange()) {
// Use a safer version that doesn't trigger UI updates
// during the first drawing operation
static bool isFirstOperation = true;
if (!isFirstOperation) {
g_gui.UpdateTitle();
} else {
isFirstOperation = false;
}
}
// Special handling for remote actions
if (batch->type == ACTION_REMOTE) {
try {
// For remote actions, we need to be extra careful
// First clear the batch to ensure no dangling pointers
ActionVector batchCopy;
// Swap the batch contents to avoid issues during deletion
batchCopy.swap(batch->batch);
// Now safely delete the batch (which should be empty)
delete batch;
// Then safely delete each action in the copy
for (Action* action : batchCopy) {
try {
if (action) {
delete action;
}
} catch (const std::exception& e) {
// Log error but continue cleanup
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Error deleting remote action: " << e.what() << "\n";
logFile.close();
}
}
}
} catch (const std::exception& e) {
// Log error but don't crash
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Error handling remote batch: " << e.what() << "\n";
logFile.close();
}
}
return;
}
// Protect against memory corruption when clearing redo history
while (current < actions.size()) {
try {
if (actions.empty()) break;
BatchAction* todelete = actions.back();
actions.pop_back(); // Remove from container before deleting to avoid invalid references
if (todelete) {
memory_size -= todelete->memsize();
delete todelete;
}
} catch (const std::exception& e) {
// Log error but continue processing
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Error clearing action: " << e.what() << "\n";
logFile.close();
}
}
}
// Safely manage memory for undo size limits
try {
// Limit by memory size
while (memory_size > size_t(1024 * 1024 * g_settings.getInteger(Config::UNDO_MEM_SIZE)) && !actions.empty()) {
BatchAction* todelete = actions.front();
actions.pop_front(); // Remove from container before accessing or deleting
if (todelete) {
memory_size -= todelete->memsize();
delete todelete;
}
if (current > 0) {
current--;
}
}
// Limit by action count
while (actions.size() > size_t(g_settings.getInteger(Config::UNDO_SIZE)) && !actions.empty()) {
BatchAction* todelete = actions.front();
actions.pop_front(); // Remove from container before accessing or deleting
if (todelete) {
memory_size -= todelete->memsize();
delete todelete;
}
if (current > 0) {
current--;
}
}
// Process action with additional safety
bool actionMerged = false;
if (!actions.empty()) {
BatchAction* lastAction = actions.back();
if (lastAction && batch &&
lastAction->type == batch->type &&
g_settings.getInteger(Config::GROUP_ACTIONS) &&
time(nullptr) - stacking_delay < lastAction->timestamp) {
// Save current memory size before merge
memory_size -= lastAction->memsize();
// Perform the merge
lastAction->merge(batch);
lastAction->timestamp = time(nullptr);
// Update memory size after merge
memory_size += lastAction->memsize(true);
delete batch;
actionMerged = true;
}
}
// Add as new action if not merged
if (!actionMerged) {
memory_size += batch->memsize();
actions.push_back(batch);
batch->timestamp = time(nullptr);
current++;
}
} catch (const std::exception& e) {
// Log error but don't crash
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Error processing batch: " << e.what() << "\n";
logFile.close();
}
// If we caught an exception and haven't added or deleted the batch yet, delete it now
if (batch) {
delete batch;
}
}
} catch (const std::exception& e) {
// Last resort error handler for entire function
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Critical error in addBatch: " << e.what() << "\n";
logFile.close();
}
// Clean up batch if we haven't processed it yet
if (batch) {
delete batch;
}
}
}
void ActionQueue::undo() {
try {
if (current > 0 && current <= actions.size()) {
current--;
BatchAction* batch = actions[current];
if (batch) {
batch->undo();
}
}
} catch (const std::exception& e) {
// Log error but don't crash
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Error in undo(): " << e.what() << "\n";
logFile.close();
}
}
}
void ActionQueue::redo() {
try {
if (current < actions.size()) {
BatchAction* batch = actions[current];
if (batch) {
batch->redo();
}
current++;
}
} catch (const std::exception& e) {
// Log error but don't crash
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Error in redo(): " << e.what() << "\n";
logFile.close();
}
}
}
void ActionQueue::clear() {
try {
// Make a copy of the actions to avoid modifying the container while iterating
ActionList actionsCopy = actions;
// Clear the original container first to prevent double deletions
actions.clear();
current = 0;
memory_size = 0;
// Now safely delete each action
for (BatchAction* action : actionsCopy) {
try {
if (action) {
delete action;
}
} catch (const std::exception& e) {
// Log error but continue cleanup
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Error in clear() deleting action: " << e.what() << "\n";
logFile.close();
}
}
}
} catch (const std::exception& e) {
// Log error but don't crash
std::ofstream logFile((wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "action_error.log").ToStdString(), std::ios::app);
if (logFile.is_open()) {
wxDateTime now = wxDateTime::Now();
logFile << now.FormatISOCombined() << ": Critical error in clear(): " << e.what() << "\n";
logFile.close();
}
// Reset state as much as possible
actions.clear();
current = 0;
memory_size = 0;
}
}
DirtyList::DirtyList() :
owner(0) {
;
}
DirtyList::~DirtyList() {
;
}
void DirtyList::AddPosition(int x, int y, int z) {
uint32_t m = ((x >> 2) << 18) | ((y >> 2) << 4);
ValueType fi = { m, 0 };
SetType::iterator s = iset.find(fi);
if (s != iset.end()) {
ValueType v = *s;
iset.erase(s);
v.floors = (1 << z) | v.floors;
iset.insert(v);
} else {
ValueType v = { m, (uint32_t)(1 << z) };
iset.insert(v);
}
}
void DirtyList::AddChange(Change* c) {
ichanges.push_back(c);
}
DirtyList::SetType& DirtyList::GetPosList() {
return iset;
}
ChangeList& DirtyList::GetChanges() {
return ichanges;
}