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);