diff --git a/source.list b/source.list
index 92160562da..aa0d3017e4 100644
--- a/source.list
+++ b/source.list
@@ -1240,3 +1240,6 @@ tracerestrict.h
tracerestrict.cpp
tracerestrict_gui.cpp
saveload/tracerestrict_sl.cpp
+
+scope_info.cpp
+scope_info.h
diff --git a/src/command.cpp b/src/command.cpp
index 3a15cd7c90..388985da64 100644
--- a/src/command.cpp
+++ b/src/command.cpp
@@ -26,6 +26,9 @@
#include "signal_func.h"
#include "core/backup_type.hpp"
#include "object_base.h"
+#include "newgrf_text.h"
+#include "string_func.h"
+#include "scope_info.h"
#include "table/strings.h"
@@ -528,6 +531,8 @@ CommandCost DoCommand(const CommandContainer *container, DoCommandFlag flags)
*/
CommandCost DoCommand(TileIndex tile, uint32 p1, uint32 p2, DoCommandFlag flags, uint32 cmd, const char *text)
{
+ SCOPE_INFO_FMT([=], "DoCommand: tile: %dx%d, p1: 0x%X, p2: 0x%X, flags: 0x%X, company: %s, cmd: 0x%X (%s)", TileX(tile), TileY(tile), p1, p2, flags, DumpCompanyInfo(_current_company), cmd, GetCommandName(cmd));
+
CommandCost res;
/* Do not even think about executing out-of-bounds tile-commands */
@@ -622,6 +627,8 @@ bool DoCommandP(const CommandContainer *container, bool my_cmd)
*/
bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd, uint32 binary_length)
{
+ SCOPE_INFO_FMT([=], "DoCommandP: tile: %dx%d, p1: 0x%X, p2: 0x%X, company: %s, cmd: 0x%X (%s), my_cmd: %d", TileX(tile), TileY(tile), p1, p2, DumpCompanyInfo(_current_company), cmd, GetCommandName(cmd), my_cmd);
+
/* Cost estimation is generally only done when the
* local user presses shift while doing somthing.
* However, in case of incoming network commands,
@@ -811,7 +818,9 @@ CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd,
* test and execution have yielded the same result,
* i.e. cost and error state are the same. */
if (!test_and_exec_can_differ) {
- assert(res.GetCost() == res2.GetCost() && res.Failed() == res2.Failed()); // sanity check
+ assert_msg(res.GetCost() == res2.GetCost() && res.Failed() == res2.Failed(),
+ "Command: cmd: 0x%X (%s), Test: %s, Exec: %s", cmd, GetCommandName(cmd),
+ res.AllocSummaryMessage(GB(cmd, 16, 16)), res2.AllocSummaryMessage(GB(cmd, 16, 16))); // sanity check
} else if (res2.Failed()) {
return_dcpi(res2);
}
@@ -879,3 +888,34 @@ void CommandCost::UseTextRefStack(const GRFFile *grffile, uint num_registers)
textref_stack[i] = _temp_store.GetValue(0x100 + i);
}
}
+
+char *CommandCost::AllocSummaryMessage(StringID cmd_msg) const
+{
+ char buf[DRAW_STRING_BUFFER];
+ this->WriteSummaryMessage(buf, lastof(buf), cmd_msg);
+ return stredup(buf, lastof(buf));
+}
+
+int CommandCost::WriteSummaryMessage(char *buf, char *last, StringID cmd_msg) const
+{
+ if (this->Succeeded()) {
+ return seprintf(buf, last, "Success: cost: " OTTD_PRINTF64, (int64) this->GetCost());
+ } else {
+ if (this->textref_stack_size > 0) StartTextRefStackUsage(this->textref_stack_grffile, this->textref_stack_size, textref_stack);
+
+ char *b = buf;
+ b += seprintf(b, last, "Failed: cost: " OTTD_PRINTF64, (int64) this->GetCost());
+ if (cmd_msg != 0) {
+ b += seprintf(b, last, " ");
+ b = GetString(b, cmd_msg, last);
+ }
+ if (this->message != INVALID_STRING_ID) {
+ b += seprintf(b, last, " ");
+ b = GetString(b, this->message, last);
+ }
+
+ if (this->textref_stack_size > 0) StopTextRefStackUsage();
+
+ return b - buf;
+ }
+}
diff --git a/src/command_type.h b/src/command_type.h
index da4615d0b0..842248f4b5 100644
--- a/src/command_type.h
+++ b/src/command_type.h
@@ -162,6 +162,21 @@ public:
{
return !this->success;
}
+
+ /**
+ * @param cmd_msg optional failure string as passed to DoCommand
+ * @return an allocated string summarising the command result
+ */
+ char *AllocSummaryMessage(StringID cmd_msg = 0) const;
+
+ /**
+ * Write a string summarising the command result
+ * @param buf buffer to write to
+ * @param last last byte in buffer
+ * @param cmd_msg optional failure string as passed to DoCommand
+ * @return the number of bytes written
+ */
+ int WriteSummaryMessage(char *buf, char *last, StringID cmd_msg = 0) const;
};
/**
diff --git a/src/crashlog.cpp b/src/crashlog.cpp
index 789e3bfdab..7717655711 100644
--- a/src/crashlog.cpp
+++ b/src/crashlog.cpp
@@ -28,6 +28,8 @@
#include "network/network.h"
#include "language.h"
#include "fontcache.h"
+#include "scope_info.h"
+#include "thread/thread.h"
#include "ai/ai_info.hpp"
#include "game/game.hpp"
@@ -327,6 +329,13 @@ char *CrashLog::FillCrashLog(char *buffer, const char *last) const
buffer += seprintf(buffer, last, "In game date: %i-%02i-%02i (%i, %i)\n\n", ymd.year, ymd.month + 1, ymd.day, _date_fract, _tick_skip_counter);
buffer = this->LogError(buffer, last, CrashLog::message);
+
+#ifdef USE_SCOPE_INFO
+ if (IsMainThread()) {
+ buffer += WriteScopeLog(buffer, last);
+ }
+#endif
+
buffer = this->LogOpenTTDVersion(buffer, last);
buffer = this->LogRegisters(buffer, last);
buffer = this->LogStacktrace(buffer, last);
diff --git a/src/openttd.cpp b/src/openttd.cpp
index cb33f8b748..8d8142c407 100644
--- a/src/openttd.cpp
+++ b/src/openttd.cpp
@@ -67,6 +67,7 @@
#include "programmable_signals.h"
#include "smallmap_gui.h"
#include "viewport_func.h"
+#include "thread/thread.h"
#include "linkgraph/linkgraphschedule.h"
#include "tracerestrict.h"
@@ -128,6 +129,25 @@ void CDECL error(const char *s, ...)
abort();
}
+void CDECL assert_msg_error(int line, const char *file, const char *expr, const char *str, ...)
+{
+ va_list va;
+ char buf[2048];
+
+ char *b = buf;
+ b += seprintf(b, lastof(buf), "Assertion failed at line %i of %s: %s\n\t", line, file, expr);
+
+ va_start(va, str);
+ vseprintf(b, lastof(buf), str, va);
+ va_end(va);
+
+ ShowOSErrorBox(buf, true);
+
+ /* Set the error message for the crash log and then invoke it. */
+ CrashLog::SetErrorMessage(buf);
+ abort();
+}
+
/**
* Shows some information on the console/a popup box depending on the OS.
* @param str the text to show.
@@ -545,6 +565,7 @@ static const OptionData _options[] = {
*/
int openttd_main(int argc, char *argv[])
{
+ SetSelfAsMainThread();
char *musicdriver = NULL;
char *sounddriver = NULL;
char *videodriver = NULL;
diff --git a/src/scope_info.cpp b/src/scope_info.cpp
new file mode 100644
index 0000000000..7e5aa1cc23
--- /dev/null
+++ b/src/scope_info.cpp
@@ -0,0 +1,72 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD 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, version 2.
+ * OpenTTD 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 OpenTTD. If not, see .
+ */
+
+/** @file scope_info.cpp Scope info debug functions. */
+
+#include "stdafx.h"
+#include "scope_info.h"
+#include "string_func.h"
+#include "strings_func.h"
+#include "company_base.h"
+#include "vehicle_base.h"
+#include "table/strings.h"
+
+#include "safeguards.h"
+
+#ifdef USE_SCOPE_INFO
+
+std::vector> _scope_stack;
+
+int WriteScopeLog(char *buf, const char *last)
+{
+ char *b = buf;
+ if (!_scope_stack.empty()) {
+ b += seprintf(b, last, "Within context:");
+ int depth = 0;
+ for (auto it = _scope_stack.rbegin(); it != _scope_stack.rend(); ++it, depth++) {
+ b += seprintf(b, last, "\n %2d: ", depth);
+ b += (*it)(b, last);
+ }
+ b += seprintf(b, last, "\n\n");
+ }
+ return b - buf;
+}
+
+// helper functions
+char *DumpCompanyInfo(int company_id)
+{
+ char buf[256];
+ char *b = buf + seprintf(buf, lastof(buf), "%d (", company_id);
+ SetDParam(0, company_id);
+ b = GetString(b, STR_COMPANY_NAME, lastof(buf));
+ b += seprintf(b, lastof(buf), ")");
+ return stredup(buf, lastof(buf));
+}
+
+char *DumpVehicleInfo(const Vehicle *v)
+{
+ char buf[256];
+ char *b = buf;
+ if (v) {
+ b += seprintf(b, lastof(buf), "veh: %u: (", v->index);
+ SetDParam(0, v->index);
+ b = GetString(b, STR_VEHICLE_NAME, lastof(buf));
+ if (v->First() && v->First() != v) {
+ b += seprintf(b, lastof(buf), "), front: %u: (", v->First()->index);
+ SetDParam(0, v->First()->index);
+ b = GetString(b, STR_VEHICLE_NAME, lastof(buf));
+ }
+ b += seprintf(b, lastof(buf), ")");
+ } else {
+ b += seprintf(b, lastof(buf), "veh: NULL");
+ }
+ return stredup(buf, lastof(buf));
+}
+
+#endif
diff --git a/src/scope_info.h b/src/scope_info.h
new file mode 100644
index 0000000000..cb6cb18dd4
--- /dev/null
+++ b/src/scope_info.h
@@ -0,0 +1,58 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD 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, version 2.
+ * OpenTTD 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 OpenTTD. If not, see .
+ */
+
+/** @file scope_info.h Scope info debug functions. */
+
+#ifndef SCOPE_INFO_H
+#define SCOPE_INFO_H
+
+#ifdef USE_SCOPE_INFO
+#include
+#include
+
+struct Vehicle;
+
+extern std::vector> _scope_stack;
+
+struct scope_info_func_obj {
+ scope_info_func_obj(std::function func)
+ {
+ _scope_stack.emplace_back(std::move(func));
+ }
+
+ scope_info_func_obj(const scope_info_func_obj ©src) = delete;
+
+ ~scope_info_func_obj()
+ {
+ _scope_stack.pop_back();
+ }
+};
+
+int WriteScopeLog(char *buf, const char *last);
+
+#define SCOPE_INFO_PASTE(a, b) a ## b
+
+/**
+ * This creates a lambda in the current scope with the specified capture which outputs the given args as a format string.
+ * This lambda is then captured by reference in a std::function which is pushed onto the scope stack
+ * The scope stack is popped at the end of the scope
+ */
+#define SCOPE_INFO_FMT(capture, ...) auto SCOPE_INFO_PASTE(_sc_lm_, __LINE__) = capture (char *buf, const char *last) { return seprintf(buf, last, __VA_ARGS__); }; scope_info_func_obj SCOPE_INFO_PASTE(_sc_obj_, __LINE__) ([&](char *buf, const char *last) -> int { return SCOPE_INFO_PASTE(_sc_lm_, __LINE__) (buf, last); });
+
+// helper functions
+char *DumpCompanyInfo(int company_id);
+char *DumpVehicleInfo(const Vehicle *v);
+
+#else /* USE_SCOPE_INFO */
+
+#define SCOPE_INFO_FMT(...)
+
+#endif /* USE_SCOPE_INFO */
+
+#endif /* SCOPE_INFO_H */
diff --git a/src/stdafx.h b/src/stdafx.h
index abb0f8c304..df82ed9607 100644
--- a/src/stdafx.h
+++ b/src/stdafx.h
@@ -446,6 +446,7 @@ assert_compile(SIZE_MAX >= UINT32_MAX);
void NORETURN CDECL usererror(const char *str, ...) WARN_FORMAT(1, 2);
void NORETURN CDECL error(const char *str, ...) WARN_FORMAT(1, 2);
+void NORETURN CDECL assert_msg_error(int line, const char *file, const char *expr, const char *str, ...) WARN_FORMAT(4, 5);
#define NOT_REACHED() error("NOT_REACHED triggered at line %i of %s", __LINE__, __FILE__)
/* For non-debug builds with assertions enabled use the special assertion handler:
@@ -460,6 +461,9 @@ void NORETURN CDECL error(const char *str, ...) WARN_FORMAT(1, 2);
/* Asserts are enabled if NDEBUG isn't defined, or if we are using MSVC and WITH_ASSERT is defined. */
#if !defined(NDEBUG) || (defined(_MSC_VER) && defined(WITH_ASSERT))
#define OTTD_ASSERT
+ #define assert_msg(expression, ...) if (!(expression)) assert_msg_error(__LINE__, __FILE__, #expression, __VA_ARGS__);
+#else
+ #define assert_msg(expression, ...)
#endif
#if defined(MORPHOS) || defined(__NDS__) || defined(__DJGPP__)
@@ -550,4 +554,10 @@ static inline void free(const void *ptr)
#define unlikely(x) (x)
#endif
+#if !defined(DISABLE_SCOPE_INFO) && (__cplusplus >= 201103L || defined(__STDCXX_VERSION__) || defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__GXX_EXPERIMENTAL_CPP0X__))
+#define USE_SCOPE_INFO
+#endif
+
+#define SINGLE_ARG(...) __VA_ARGS__
+
#endif /* STDAFX_H */
diff --git a/src/thread/thread.h b/src/thread/thread.h
index 07831bb4ba..dddd26f63f 100644
--- a/src/thread/thread.h
+++ b/src/thread/thread.h
@@ -123,4 +123,14 @@ private:
*/
uint GetCPUCoreCount();
+/**
+ * Set the current thread as the "main" thread
+ */
+void SetSelfAsMainThread();
+
+/**
+ * @return true if the current thread definitely the "main" thread. If in doubt returns false.
+ */
+bool IsMainThread();
+
#endif /* THREAD_H */
diff --git a/src/thread/thread_morphos.cpp b/src/thread/thread_morphos.cpp
index 6d00d0579c..8901339c83 100644
--- a/src/thread/thread_morphos.cpp
+++ b/src/thread/thread_morphos.cpp
@@ -199,3 +199,7 @@ private:
if (thread != NULL) *thread = to;
return true;
}
+
+void SetSelfAsMainThread() { }
+
+bool IsMainThread() { return false; }
diff --git a/src/thread/thread_none.cpp b/src/thread/thread_none.cpp
index 91eb50b113..b68b372f77 100644
--- a/src/thread/thread_none.cpp
+++ b/src/thread/thread_none.cpp
@@ -33,3 +33,7 @@ public:
{
return new ThreadMutex_None();
}
+
+void SetSelfAsMainThread() { }
+
+bool IsMainThread() { return true; }
diff --git a/src/thread/thread_os2.cpp b/src/thread/thread_os2.cpp
index c66e2ad643..6cb182bae7 100644
--- a/src/thread/thread_os2.cpp
+++ b/src/thread/thread_os2.cpp
@@ -145,3 +145,7 @@ public:
{
return new ThreadMutex_OS2();
}
+
+void SetSelfAsMainThread() { }
+
+bool IsMainThread() { return false; }
diff --git a/src/thread/thread_pthread.cpp b/src/thread/thread_pthread.cpp
index 747b8943d6..6690badecf 100644
--- a/src/thread/thread_pthread.cpp
+++ b/src/thread/thread_pthread.cpp
@@ -182,3 +182,15 @@ public:
{
return new ThreadMutex_pthread();
}
+
+static pthread_t main_thread;
+
+void SetSelfAsMainThread()
+{
+ main_thread = pthread_self();
+}
+
+bool IsMainThread()
+{
+ return main_thread == pthread_self();
+}
diff --git a/src/thread/thread_win32.cpp b/src/thread/thread_win32.cpp
index 81a7212530..5e158c2aca 100644
--- a/src/thread/thread_win32.cpp
+++ b/src/thread/thread_win32.cpp
@@ -158,3 +158,15 @@ public:
{
return new ThreadMutex_Win32();
}
+
+static uint main_thread_id;
+
+void SetSelfAsMainThread()
+{
+ main_thread_id = GetCurrentThreadId();
+}
+
+bool IsMainThread()
+{
+ return main_thread_id == GetCurrentThreadId();
+}
diff --git a/src/vehicle.cpp b/src/vehicle.cpp
index 122f6b3210..9ab24f787c 100644
--- a/src/vehicle.cpp
+++ b/src/vehicle.cpp
@@ -55,6 +55,8 @@
#include "linkgraph/refresh.h"
#include "blitter/factory.hpp"
#include "tbtr_template_vehicle_func.h"
+#include "string_func.h"
+#include "scope_info.h"
#include "table/strings.h"
@@ -944,8 +946,10 @@ static void RunVehicleDayProc()
if (_game_mode != GM_NORMAL) return;
/* Run the day_proc for every DAY_TICKS vehicle starting at _date_fract. */
+ Vehicle *v = NULL;
+ SCOPE_INFO_FMT([&v], "RunVehicleDayProc: %s", DumpVehicleInfo(v));
for (size_t i = _date_fract; i < Vehicle::GetPoolSize(); i += DAY_TICKS) {
- Vehicle *v = Vehicle::Get(i);
+ v = Vehicle::Get(i);
if (v == NULL) continue;
/* Call the 32-day callback if needed */
@@ -998,7 +1002,8 @@ void CallVehicleTicks()
Station *st;
FOR_ALL_STATIONS(st) LoadUnloadStation(st);
- Vehicle *v;
+ Vehicle *v = NULL;
+ SCOPE_INFO_FMT([&v], "CallVehicleTicks: %s", DumpVehicleInfo(v));
FOR_ALL_VEHICLES(v) {
/* Vehicle could be deleted in this tick */
if (!v->Tick()) {
@@ -1076,6 +1081,7 @@ void CallVehicleTicks()
}
}
}
+ v = NULL;
/* do Auto Replacement */
Backup cur_company(_current_company, FILE_LINE);