From 39240dba88e46bd00d6de581a6a5a542a035bf44 Mon Sep 17 00:00:00 2001 From: Karol Nowak Date: Wed, 4 Feb 2009 21:48:16 +0000 Subject: [PATCH] Optimized handling of friends/ignores lists. These are now backed by a set, instead of doing string operations on preferences. This resolves the problem of very high CPU usage in multiplayer lobby when friend/ignore lists are long. --- src/CMakeLists.txt | 1 + src/Makefile.am | 1 + src/SConscript | 1 + src/game_preferences.cpp | 73 ++++++++++++++++-------------- src/game_preferences.hpp | 4 +- src/game_preferences_display.cpp | 8 ++-- src/menu_events.cpp | 36 ++++++++++----- src/serialization/string_utils.cpp | 12 ----- src/serialization/string_utils.hpp | 15 +++++- src/tests/test_serialization.cpp | 36 +++++++++++++++ 10 files changed, 124 insertions(+), 63 deletions(-) create mode 100644 src/tests/test_serialization.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b31300bce0f..4d3df245eb8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -476,6 +476,7 @@ SET(test_SRC tests/test_policy.cpp tests/test_team.cpp tests/test_util.cpp + tests/test_serialization.cpp tests/test_version.cpp tests/gui/test_save_dialog.cpp tests/gui/test_drop_target.cpp diff --git a/src/Makefile.am b/src/Makefile.am index a87b2c66781..639e7d8ce0b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -289,6 +289,7 @@ test_SOURCES = \ tests/test_policy.cpp \ tests/test_team.cpp \ tests/test_util.cpp \ + tests/test_serialization.cpp \ tests/test_version.cpp \ tests/gui/test_save_dialog.cpp \ tests/gui/test_drop_target.cpp \ diff --git a/src/SConscript b/src/SConscript index 5c3ed2fa278..ea1e13ab9b2 100644 --- a/src/SConscript +++ b/src/SConscript @@ -371,6 +371,7 @@ test_sources = Split(""" tests/test_policy.cpp tests/test_team.cpp tests/test_util.cpp + tests/test_serialization.cpp tests/test_version.cpp tests/gui/test_drop_target.cpp tests/gui/test_save_dialog.cpp diff --git a/src/game_preferences.cpp b/src/game_preferences.cpp index 616baaaa09c..6e8986e5995 100644 --- a/src/game_preferences.cpp +++ b/src/game_preferences.cpp @@ -39,19 +39,10 @@ std::set encountered_terrains_set; std::map > history_map; const unsigned max_history_saved = 50; -/** Add a nick to the specified relation setting. */ -void add_relation(const std::string& nick, const std::string& relation) { - std::vector r = utils::split(preferences::get(relation)); - r.push_back(nick); - preferences::set(relation, utils::join(r)); -} - -/** Remove a nick from the specified relation setting. */ -void remove_relation(const std::string& nick, const std::string& relation) { - std::vector r = utils::split(preferences::get(relation)); - r.erase(std::remove(r.begin(), r.end(), nick), r.end()); - preferences::set(relation, utils::join(r)); -} +std::set friends; +std::set ignores; +bool friends_initialized = false; +bool ignores_initialized = false; } // anon namespace @@ -149,32 +140,58 @@ manager::~manager() set_ping_timeout(network::ping_timeout); } -std::string get_friends() { - return preferences::get("friends"); +const std::set & get_friends() { + if(!friends_initialized) { + std::vector names = utils::split(preferences::get("friends")); + std::set tmp(names.begin(), names.end()); + friends.swap(tmp); + + friends_initialized = true; + } + + return friends; } -std::string get_ignores() { - return preferences::get("ignores"); +const std::set & get_ignores() { + if(!ignores_initialized) { + std::vector names = utils::split(preferences::get("ignores")); + std::set tmp(names.begin(), names.end()); + ignores.swap(tmp); + + ignores_initialized = true; + } + + return ignores; } bool add_friend(const std::string& nick) { if (!utils::isvalid_wildcard(nick)) return false; - add_relation(nick, "friends"); + friends.insert(nick); + preferences::set("friends", utils::join(friends)); return true; } bool add_ignore(const std::string& nick) { if (!utils::isvalid_wildcard(nick)) return false; - add_relation(nick, "ignores"); + ignores.insert(nick); + preferences::set("ignores", utils::join(ignores)); return true; } void remove_friend(const std::string& nick) { - remove_relation(nick, "friends"); + std::set::iterator i = friends.find(nick); + if(i != friends.end()) { + friends.erase(i); + preferences::set("friends", utils::join(friends)); + } } void remove_ignore(const std::string& nick) { - remove_relation(nick, "ignores"); + std::set::iterator i = ignores.find(nick); + if(i != ignores.end()) { + ignores.erase(i); + preferences::set("ignores", utils::join(ignores)); + } } void clear_friends() { @@ -186,21 +203,11 @@ void clear_ignores() { } bool is_friend(const std::string& nick) { - const std::vector& friends = utils::split(get_friends()); - foreach(const std::string& var, friends) { - if(utils::wildcard_string_match(nick, var)) - return true; - } - return false; + return friends.find(nick) != friends.end(); } bool is_ignored(const std::string& nick) { - const std::vector& ignores = utils::split(get_ignores()); - foreach(const std::string& var, ignores) { - if(utils::wildcard_string_match(nick, var)) - return true; - } - return false; + return ignores.find(nick) != ignores.end(); } bool show_lobby_join(const std::string& sender, const std::string& message) { diff --git a/src/game_preferences.hpp b/src/game_preferences.hpp index fce96b32ad7..8ead4be7a70 100644 --- a/src/game_preferences.hpp +++ b/src/game_preferences.hpp @@ -42,8 +42,8 @@ namespace preferences { void _set_lobby_joins(int show); enum { SHOW_NONE, SHOW_FRIENDS, SHOW_ALL }; - std::string get_friends(); - std::string get_ignores(); + const std::set & get_friends(); + const std::set & get_ignores(); bool add_friend(const std::string& nick); bool add_ignore(const std::string& nick); void remove_friend(const std::string& nick); diff --git a/src/game_preferences_display.cpp b/src/game_preferences_display.cpp index 88ca9bcdf31..bede8cb9d68 100644 --- a/src/game_preferences_display.cpp +++ b/src/game_preferences_display.cpp @@ -993,12 +993,14 @@ void preferences_dialog::set_advanced_menu() void preferences_dialog::set_friends_menu() { - const std::vector& friends = utils::split(preferences::get_friends()); - const std::vector& ignores = utils::split(preferences::get_ignores()); + const std::set& friends = preferences::get_friends(); + const std::set& ignores = preferences::get_ignores(); + std::vector friends_items; std::vector friends_names; std::string const imgpre = IMAGE_PREFIX + std::string("misc/status-"); - std::vector::const_iterator i; + + std::set::const_iterator i; for (i = friends.begin(); i != friends.end(); ++i) { friends_items.push_back(imgpre + "friend.png" + COLUMN_SEPARATOR diff --git a/src/menu_events.cpp b/src/menu_events.cpp index 9b06e16d08a..55e264eed18 100644 --- a/src/menu_events.cpp +++ b/src/menu_events.cpp @@ -2449,14 +2449,17 @@ private: chat_command_handler cch(*this, allies_only); cch.dispatch(cmd); } + void chat_command_handler::do_emote() { chat_handler_.send_chat_message("/me " + get_data(), allies_only_); } + void chat_command_handler::do_network_send() { chat_handler_.send_command(get_cmd(), get_data()); } + void chat_command_handler::do_whisper() { if (get_data(1).empty()) return command_failed_need_arg(1); @@ -2471,6 +2474,7 @@ private: cwhisper["message"], game_display::MESSAGE_PRIVATE); network::send_data(data, 0, true); } + void chat_command_handler::do_log() { chat_handler_.change_logging(get_data()); @@ -2479,10 +2483,10 @@ private: void chat_command_handler::do_ignore() { if (get_arg(1).empty()) { - const std::string& tmp = preferences::get_ignores(); - print("ignores list", tmp.empty() ? "(empty)" : tmp); + const std::set& tmp = preferences::get_ignores(); + print("ignores list", tmp.empty() ? "(empty)" : utils::join(tmp)); } else { - for(int i = 1;!get_arg(i).empty();i++){ + for(int i = 1; !get_arg(i).empty(); i++){ if (preferences::add_ignore(get_arg(i))) { print("ignore", _("Added to ignore list: ") + get_arg(i)); } else { @@ -2491,11 +2495,12 @@ private: } } } + void chat_command_handler::do_friend() { if (get_arg(1).empty()) { - const std::string& tmp = preferences::get_friends(); - print("friends list", tmp.empty() ? "(empty)" : tmp); + const std::set& tmp = preferences::get_friends(); + print("friends list", tmp.empty() ? "(empty)" : utils::join(tmp)); } else { for(int i = 1;!get_arg(i).empty();i++){ if (preferences::add_friend(get_arg(i))) { @@ -2506,6 +2511,7 @@ private: } } } + void chat_command_handler::do_remove() { for(int i = 1;!get_arg(i).empty();i++){ @@ -2514,19 +2520,25 @@ private: print("list", _("Removed from list: ") + get_arg(i)); } } + void chat_command_handler::do_display() { - const std::string& text_friend = preferences::get_friends(); - const std::string& text_ignore = preferences::get_ignores(); - if (!text_friend.empty()) { - print("friends list", text_friend); + const std::set & friends = preferences::get_friends(); + const std::set & ignores = preferences::get_ignores(); + + if (!friends.empty()) { + print("friends list", utils::join(friends)); } - if (!text_ignore.empty()) { - print("ignores list", text_ignore); - } else if (text_friend.empty()) { + + if (!ignores.empty()) { + print("ignores list", utils::join(ignores)); + } + + if (friends.empty() && ignores.empty()) { print("list", _("There are no players on your friends or ignore list.")); } } + void chat_command_handler::do_version() { print("version", game_config::revision); } diff --git a/src/serialization/string_utils.cpp b/src/serialization/string_utils.cpp index a420c334894..c86011aedcc 100644 --- a/src/serialization/string_utils.cpp +++ b/src/serialization/string_utils.cpp @@ -390,18 +390,6 @@ bool wildcard_string_match(const std::string& str, const std::string& match) { return matches; } -std::string join(std::vector< std::string > const &v, char c) -{ - std::stringstream str; - for(std::vector< std::string >::const_iterator i = v.begin(); i != v.end(); ++i) { - str << *i; - if (i + 1 != v.end()) - str << c; - } - - return str.str(); -} - std::vector< std::string > quoted_split(std::string const &val, char c, int flags, char quote) { std::vector res; diff --git a/src/serialization/string_utils.hpp b/src/serialization/string_utils.hpp index 139c0888695..e682c2c0d64 100644 --- a/src/serialization/string_utils.hpp +++ b/src/serialization/string_utils.hpp @@ -20,8 +20,10 @@ #include #include +#include #include #include +#include #include "../tstring.hpp" #include "../util.hpp" @@ -77,7 +79,18 @@ std::vector< std::string > paranthetical_split(std::string const &val, const char separator = 0 , std::string const &left="(", std::string const &right=")",int flags = REMOVE_EMPTY | STRIP_SPACES); -std::string join(std::vector< std::string > const &v, char c = ','); +template +std::string join(T const &v, char c = ',') +{ + std::stringstream str; + for(typename T::const_iterator i = v.begin(); i != v.end(); ++i) { + str << *i; + if (boost::next(i) != v.end()) + str << c; + } + + return str.str(); +} /** * This function is identical to split(), except it does not split diff --git a/src/tests/test_serialization.cpp b/src/tests/test_serialization.cpp new file mode 100644 index 00000000000..9f2d751baf8 --- /dev/null +++ b/src/tests/test_serialization.cpp @@ -0,0 +1,36 @@ +/* $Id$ */ +/* + Copyright (C) 2009 by Karol Nowak + Part of the Battle for Wesnoth Project http://www.wesnoth.org/ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + or at your option any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY. + + See the COPYING file for more details. +*/ +#include +#include +#include "serialization/string_utils.hpp" +#include + +BOOST_AUTO_TEST_CASE( utils_join_test ) +{ + BOOST_MESSAGE( "Starting utils::join test!" ); + + std::vector fruit; + + BOOST_CHECK( utils::join(fruit) == "" ); + + fruit.push_back("apples"); + + BOOST_CHECK( utils::join(fruit) == "apples" ); + + fruit.push_back("oranges"); + fruit.push_back("lemons"); + + BOOST_CHECK( utils::join(fruit) == "apples,oranges,lemons" ); +} +