diff --git a/src/ai/api/ai_log.cpp b/src/ai/api/ai_log.cpp index 7a269030d3..af2295df1b 100644 --- a/src/ai/api/ai_log.cpp +++ b/src/ai/api/ai_log.cpp @@ -75,7 +75,7 @@ /* Also still print to debug window */ DEBUG(ai, level, "[%d] [%c] %s", (uint)_current_company, logc, log->lines[log->pos]); - InvalidateWindowData(WC_AI_DEBUG, 0, _current_company); + InvalidateWindowData(WC_AI_DEBUG, 0, _current_company, true); // breakpoint handling needs calling Invalidate immediately. } /* static */ void AILog::FreeLogPointer() diff --git a/src/hotkeys.cpp b/src/hotkeys.cpp index 686877bf38..5ad8f2bf95 100644 --- a/src/hotkeys.cpp +++ b/src/hotkeys.cpp @@ -74,7 +74,8 @@ static uint16 ParseCode(const char *start, const char *end) } if (end - start == 1) { if (*start >= 'a' && *start <= 'z') return *start - ('a'-'A'); - return *start; + /* Ignore invalid keycodes */ + if (*(uint8*)start < 128) return *start; } return 0; } @@ -99,10 +100,6 @@ static uint16 ParseKeycode(const char *start, const char *end) if (code & ~WKC_SPECIAL_KEYS) return 0; keycode |= code; } else { - /* Ignore invalid keycodes */ - if (code >= 128) { - return 0; - } /* Ignore the code if it has more then 1 letter. */ if (keycode & ~WKC_SPECIAL_KEYS) return 0; keycode |= code; diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 3f43dcdc88..ed85860da5 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -435,7 +435,7 @@ struct MainWindow : Window virtual void OnInvalidateData(int data) { /* Forward the message to the appropiate toolbar (ingame or scenario editor) */ - InvalidateWindowData(WC_MAIN_TOOLBAR, 0, data); + InvalidateWindowData(WC_MAIN_TOOLBAR, 0, data, true); } static Hotkey global_hotkeys[]; diff --git a/src/misc_gui.cpp b/src/misc_gui.cpp index 33c22ed7a2..7d7aa37faa 100644 --- a/src/misc_gui.cpp +++ b/src/misc_gui.cpp @@ -198,6 +198,7 @@ public: if (c != NULL) { Money old_money = c->money; c->money = INT64_MAX; + assert(_current_company == _local_company); CommandCost costclear = DoCommand(tile, 0, 0, DC_NONE, CMD_LANDSCAPE_CLEAR); c->money = old_money; if (costclear.Succeeded()) { @@ -329,6 +330,16 @@ public: { ::ShowNewGRFInspectWindow(GetGrfSpecFeature(this->tile), this->tile); } + + virtual void OnInvalidateData(int data) + { + switch (data) { + case 1: + /* ReInit, "debug" sprite might have changed */ + this->ReInit(); + break; + } + } }; /** diff --git a/src/newgrf.cpp b/src/newgrf.cpp index d987c3ed1b..e5211ccd37 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -6727,6 +6727,8 @@ static bool ChangeGRFParamValueNames(ByteReader *buf) if (type != 'T' || id > _cur_parameter->max_value) { grfmsg(2, "StaticGRFInfo: all child nodes of 'INFO'->'PARA'->param_num->'VALU' should have type 't' and the value/bit number as id"); if (!SkipUnknownInfo(buf, type)) return false; + type = buf->ReadByte(); + continue; } byte langid = buf->ReadByte(); @@ -6770,7 +6772,9 @@ static bool HandleParameterInfo(ByteReader *buf) uint32 id = buf->ReadDWord(); if (type != 'C' || id >= _cur_grfconfig->num_valid_params) { grfmsg(2, "StaticGRFInfo: all child nodes of 'INFO'->'PARA' should have type 'C' and their parameter number as id"); - return SkipUnknownInfo(buf, type); + if (!SkipUnknownInfo(buf, type)) return false; + type = buf->ReadByte(); + continue; } if (id >= _cur_grfconfig->param_info.Length()) { diff --git a/src/newgrf_debug_gui.cpp b/src/newgrf_debug_gui.cpp index f4c3b985a6..b96ecbd7f8 100644 --- a/src/newgrf_debug_gui.cpp +++ b/src/newgrf_debug_gui.cpp @@ -518,9 +518,10 @@ void DeleteNewGRFInspectWindow(GrfSpecFeature feature, uint index) WindowNumber wno = GetInspectWindowNumber(feature, index); DeleteWindowById(WC_NEWGRF_INSPECT, wno); - /* Reinitialise the land information window to remove the "debug" sprite if needed. */ - Window *w = FindWindowById(WC_LAND_INFO, 0); - if (w != NULL) w->ReInit(); + /* Reinitialise the land information window to remove the "debug" sprite if needed. + * Note: Since we might be called from a command here, it is important to not execute + * the invalidation immediately. The landinfo window tests commands itself. */ + InvalidateWindowData(WC_LAND_INFO, 0, 1); } /** diff --git a/src/saveload/saveload.cpp b/src/saveload/saveload.cpp index bff6b05d71..c4e314fedc 100644 --- a/src/saveload/saveload.cpp +++ b/src/saveload/saveload.cpp @@ -450,6 +450,11 @@ static void SlNullPointers() { _sl.action = SLA_NULL; + /* We don't want any savegame conversion code to run + * during NULLing; especially those that try to get + * pointers from other pools. */ + _sl_version = SAVEGAME_VERSION; + DEBUG(sl, 1, "Nulling pointers"); FOR_ALL_CHUNK_HANDLERS(ch) { diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 754ae91986..e234c9d28c 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -264,7 +264,7 @@ void Train::ConsistChanged(bool same_length) if (this->IsFrontEngine()) { this->UpdateAcceleration(); SetWindowDirty(WC_VEHICLE_DETAILS, this->index); - InvalidateWindowData(WC_VEHICLE_REFIT, this->index); + InvalidateWindowData(WC_VEHICLE_REFIT, this->index); // Important, do not invalidate immediately. The refit window tests commands. } } @@ -1089,7 +1089,7 @@ static void NormaliseTrainHead(Train *head) if (!head->IsFrontEngine()) return; /* Update the refit button and window */ - InvalidateWindowData(WC_VEHICLE_REFIT, head->index); + InvalidateWindowData(WC_VEHICLE_REFIT, head->index); // Important, do not invalidate immediately. The refit window tests commands. SetWindowWidgetDirty(WC_VEHICLE_VIEW, head->index, VVW_WIDGET_REFIT_VEH); /* If we don't have a unit number yet, set one. */ diff --git a/src/vehicle.cpp b/src/vehicle.cpp index ec78d8f428..d2feacc075 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2270,7 +2270,7 @@ void Vehicle::RemoveFromShared() } else if (were_first) { /* If we were the first one, update to the new first one. * Note: FirstShared() is already the new first */ - InvalidateWindowData(GetWindowClassForVehicleType(this->type), vli.Pack(), this->FirstShared()->index | (1U << 31)); + InvalidateWindowData(GetWindowClassForVehicleType(this->type), vli.Pack(), this->FirstShared()->index | (1U << 31), true); } this->next_shared = NULL; diff --git a/src/vehicle_gui.cpp b/src/vehicle_gui.cpp index 96580dac9f..6efbecf533 100644 --- a/src/vehicle_gui.cpp +++ b/src/vehicle_gui.cpp @@ -585,6 +585,7 @@ struct RefitWindow : public Window { */ StringID GetCapacityString(RefitOption *option) const { + assert(_current_company == _local_company); Vehicle *v = Vehicle::Get(this->window_number); CommandCost cost = DoCommand(v->tile, this->selected_vehicle, option->cargo | option->subtype << 8 | this->num_vehicles << 16, DC_QUERY_COST, GetCmdRefitVeh(v->type)); @@ -1119,7 +1120,7 @@ static inline void ChangeVehicleWindow(WindowClass window_class, VehicleID from_ _thd.window_number = to_index; } - /* Notify the window */ + /* Notify the window immediately, without scheduling. */ w->InvalidateData(); } } diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp index dc230456da..6aa9656be6 100644 --- a/src/video/win32_v.cpp +++ b/src/video/win32_v.cpp @@ -224,7 +224,7 @@ static void CALLBACK TrackMouseTimerProc(HWND hwnd, UINT msg, UINT event, DWORD } } -static bool MakeWindow(bool full_screen) +bool VideoDriver_Win32::MakeWindow(bool full_screen) { _fullscreen = full_screen; @@ -259,11 +259,11 @@ static bool MakeWindow(bool full_screen) if (ChangeDisplaySettings(&settings, CDS_FULLSCREEN | CDS_TEST) != DISP_CHANGE_SUCCESSFUL) { RECT r; GetWindowRect(GetDesktopWindow(), &r); - return _video_driver->ChangeResolution(r.right - r.left, r.bottom - r.top); + return this->ChangeResolution(r.right - r.left, r.bottom - r.top); } if (ChangeDisplaySettings(&settings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) { - MakeWindow(false); // don't care about the result + this->MakeWindow(false); // don't care about the result return false; // the request failed } } else if (_wnd.fullscreen) { @@ -646,7 +646,7 @@ static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lP if (active && minimized) { /* Restore the game window */ ShowWindow(hwnd, SW_RESTORE); - MakeWindow(true); + static_cast(_video_driver)->MakeWindow(true); } else if (!active && !minimized) { /* Minimise the window and restore desktop */ ShowWindow(hwnd, SW_MINIMIZE); @@ -800,7 +800,7 @@ const char *VideoDriver_Win32::Start(const char * const *parm) _wnd.height_org = _cur_resolution.height; AllocateDibSection(_cur_resolution.width, _cur_resolution.height); - MakeWindow(_fullscreen); + this->MakeWindow(_fullscreen); MarkWholeScreenDirty(); @@ -915,10 +915,10 @@ bool VideoDriver_Win32::ChangeResolution(int w, int h) _wnd.width = _wnd.width_org = w; _wnd.height = _wnd.height_org = h; - return MakeWindow(_fullscreen); // _wnd.fullscreen screws up ingame resolution switching + return this->MakeWindow(_fullscreen); // _wnd.fullscreen screws up ingame resolution switching } bool VideoDriver_Win32::ToggleFullscreen(bool full_screen) { - return MakeWindow(full_screen); + return this->MakeWindow(full_screen); } diff --git a/src/video/win32_v.h b/src/video/win32_v.h index efd82671c8..4e267b2ab0 100644 --- a/src/video/win32_v.h +++ b/src/video/win32_v.h @@ -28,6 +28,8 @@ public: /* virtual */ bool ToggleFullscreen(bool fullscreen); /* virtual */ const char *GetName() const { return "win32"; } + + bool MakeWindow(bool full_screen); }; class FVideoDriver_Win32: public VideoDriverFactory { diff --git a/src/window.cpp b/src/window.cpp index 2c6f1f1e16..57c753c52c 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -2420,6 +2420,7 @@ void UpdateWindows() if (!(w->flags4 & WF_WHITE_BORDER_MASK)) w->SetDirty(); } + w->ProcessScheduledInvalidations(); } DrawDirtyBlocks(); @@ -2476,29 +2477,47 @@ void SetWindowClassesDirty(WindowClass cls) /** * Mark window data of the window of a given class and specific window number as invalid (in need of re-computing) + * Note that by default the invalidation is not executed immediately but is scheduled till the next redraw. + * The asynchronous execution is important to prevent GUI code being executed from command scope. * @param cls Window class * @param number Window number within the class * @param data The data to invalidate with + * @param immediately If true then do not schedule the event, but execute immediately. */ -void InvalidateWindowData(WindowClass cls, WindowNumber number, int data) +void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool immediately) { Window *w; FOR_ALL_WINDOWS_FROM_BACK(w) { - if (w->window_class == cls && w->window_number == number) w->InvalidateData(data); + if (w->window_class == cls && w->window_number == number) { + if (immediately) { + w->InvalidateData(data); + } else { + w->ScheduleInvalidateData(data); + } + } } } /** * Mark window data of all windows of a given class as invalid (in need of re-computing) + * Note that by default the invalidation is not executed immediately but is scheduled till the next redraw. + * The asynchronous execution is important to prevent GUI code being executed from command scope. * @param cls Window class * @param data The data to invalidate with + * @param immediately If true then do not schedule the event, but execute immediately. */ -void InvalidateWindowClassesData(WindowClass cls, int data) +void InvalidateWindowClassesData(WindowClass cls, int data, bool immediately) { Window *w; FOR_ALL_WINDOWS_FROM_BACK(w) { - if (w->window_class == cls) w->InvalidateData(data); + if (w->window_class == cls) { + if (immediately) { + w->InvalidateData(data); + } else { + w->ScheduleInvalidateData(data); + } + } } } diff --git a/src/window_func.h b/src/window_func.h index 3d51644f6d..43f50111d4 100644 --- a/src/window_func.h +++ b/src/window_func.h @@ -34,8 +34,8 @@ void ResetWindowSystem(); void SetupColoursAndInitialWindow(); void InputLoop(); -void InvalidateWindowData(WindowClass cls, WindowNumber number, int data = 0); -void InvalidateWindowClassesData(WindowClass cls, int data = 0); +void InvalidateWindowData(WindowClass cls, WindowNumber number, int data = 0, bool immediately = false); +void InvalidateWindowClassesData(WindowClass cls, int data = 0, bool immediately = false); void DeleteNonVitalWindows(); void DeleteAllNonVitalWindows(); diff --git a/src/window_gui.h b/src/window_gui.h index 724d77880d..3254ee8c4c 100644 --- a/src/window_gui.h +++ b/src/window_gui.h @@ -17,6 +17,7 @@ #include "company_type.h" #include "tile_type.h" #include "widget_type.h" +#include "core/smallvec_type.hpp" /** State of handling an event. */ enum EventState { @@ -221,6 +222,8 @@ protected: void InitializePositionSize(int x, int y, int min_width, int min_height); void FindWindowPlacementAndResize(int def_width, int def_height); + SmallVector scheduled_invalidation_data; ///< Data of scheduled OnInvalidateData() calls. + public: Window(); @@ -438,6 +441,28 @@ public: this->OnInvalidateData(data); } + /** + * Schedule a invalidation call for next redraw. + * Important for asynchronous invalidation from commands. + * @param data The data to invalidate with + */ + void ScheduleInvalidateData(int data = 0) + { + this->SetDirty(); + *this->scheduled_invalidation_data.Append() = data; + } + + /** + * Process all scheduled invalidations. + */ + void ProcessScheduledInvalidations() + { + for (int *data = this->scheduled_invalidation_data.Begin(); this->window_class != WC_INVALID && data != this->scheduled_invalidation_data.End(); data++) { + this->OnInvalidateData(*data); + } + this->scheduled_invalidation_data.Clear(); + } + /*** Event handling ***/ /**