From 19b7249adee1dba623ba4ee69266cd13888deb3d Mon Sep 17 00:00:00 2001 From: frosch Date: Wed, 23 Feb 2011 20:54:55 +0000 Subject: (svn r22135) -Fix [FS#4523]: When commands need to invalidate windows, process these events asynchronously before the next redraw. Calling window code directly from command scope uses wrong _current_company and might issue nested DoCommands() which interfer with the running command. --- src/main_gui.cpp | 2 +- src/misc_gui.cpp | 1 + src/newgrf_debug_gui.cpp | 4 +++- src/train_cmd.cpp | 4 ++-- src/vehicle.cpp | 2 +- src/vehicle_gui.cpp | 3 ++- src/window.cpp | 27 +++++++++++++++++++++++---- src/window_func.h | 4 ++-- src/window_gui.h | 25 +++++++++++++++++++++++++ 9 files changed, 60 insertions(+), 12 deletions(-) diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 3f43dcdc8..ed85860da 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 9e770cb41..c86f6c6a3 100644 --- a/src/misc_gui.cpp +++ b/src/misc_gui.cpp @@ -199,6 +199,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()) { diff --git a/src/newgrf_debug_gui.cpp b/src/newgrf_debug_gui.cpp index 99512515f..3359f796f 100644 --- a/src/newgrf_debug_gui.cpp +++ b/src/newgrf_debug_gui.cpp @@ -518,7 +518,9 @@ 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. */ + /* 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 immediatelly. The landinfo window tests commands itself. */ InvalidateWindowData(WC_LAND_INFO, 0, 1); } diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 754ae9198..e8beb46a6 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 immediatelly. 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 immediatelly. 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 e3ffbbc91..f1dd8a7cd 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 96580dac9..61ef82ef5 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 immediatelly, without scheduling. */ w->InvalidateData(); } } diff --git a/src/window.cpp b/src/window.cpp index 2c6f1f1e1..ac4c76b3b 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 immediatelly 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 immediatelly If true then do not schedule the event, but execute immediatelly. */ -void InvalidateWindowData(WindowClass cls, WindowNumber number, int data) +void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool immediatelly) { 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 (immediatelly) { + 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 immediatelly 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 immediatelly If true then do not schedule the event, but execute immediatelly. */ -void InvalidateWindowClassesData(WindowClass cls, int data) +void InvalidateWindowClassesData(WindowClass cls, int data, bool immediatelly) { Window *w; FOR_ALL_WINDOWS_FROM_BACK(w) { - if (w->window_class == cls) w->InvalidateData(data); + if (w->window_class == cls) { + if (immediatelly) { + w->InvalidateData(data); + } else { + w->ScheduleInvalidateData(data); + } + } } } diff --git a/src/window_func.h b/src/window_func.h index 3d51644f6..933d19877 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 immediatelly = false); +void InvalidateWindowClassesData(WindowClass cls, int data = 0, bool immediatelly = false); void DeleteNonVitalWindows(); void DeleteAllNonVitalWindows(); diff --git a/src/window_gui.h b/src/window_gui.h index 724d77880..3254ee8c4 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 ***/ /** -- cgit v1.2.3-54-g00ecf