Debloat and slightly speed up the MRU code

This commit is contained in:
Thomas Goyne 2014-07-04 21:21:35 -07:00
parent 518342b919
commit d9016cc8ea
8 changed files with 161 additions and 109 deletions

View file

@ -22,35 +22,63 @@
#include "libaegisub/option.h" #include "libaegisub/option.h"
#include "libaegisub/option_value.h" #include "libaegisub/option_value.h"
namespace {
const char *mru_names[] = {
"Audio",
"Find",
"Keyframes",
"Replace",
"Subtitle",
"Timecodes",
"Video",
};
const char *option_names[] = {
"Limits/MRU",
"Limits/Find Replace",
"Limits/MRU",
"Limits/Find Replace",
"Limits/MRU",
"Limits/MRU",
"Limits/MRU",
};
int mru_index(const char *key) {
int i;
switch (*key) {
case 'A': i = 0; break;
case 'F': i = 1; break;
case 'K': i = 2; break;
case 'R': i = 3; break;
case 'S': i = 4; break;
case 'T': i = 5; break;
case 'V': i = 6; break;
default: return -1;
}
return strcmp(key, mru_names[i]) == 0 ? i : -1;
}
}
namespace agi { namespace agi {
MRUManager::MRUManager(agi::fs::path const& config, std::pair<const char *, size_t> default_config, agi::Options *options) MRUManager::MRUManager(agi::fs::path const& config, std::pair<const char *, size_t> default_config, agi::Options *options)
: config_name(config) : config_name(config)
, options(options) , options(options)
, option_names({
{"Audio", "Limits/MRU"},
{"Find", "Limits/Find Replace"},
{"Keyframes", "Limits/MRU"},
{"Replace", "Limits/Find Replace"},
{"Subtitle", "Limits/MRU"},
{"Timecodes", "Limits/MRU"},
{"Video", "Limits/MRU"},
})
{ {
LOG_D("agi/mru") << "Loading MRU List"; LOG_D("agi/mru") << "Loading MRU List";
auto root = json_util::file(config, default_config); auto root = json_util::file(config, default_config);
for (auto const& it : static_cast<json::Object const&>(root)) for (auto const& it : static_cast<json::Object const&>(root))
Load(it.first, it.second); Load(it.first.c_str(), it.second);
} }
MRUManager::MRUListMap &MRUManager::Find(std::string const& key) { MRUManager::MRUListMap &MRUManager::Find(const char *key) {
auto index = mru.find(key); auto index = mru_index(key);
if (index == mru.end()) if (index == -1)
throw MRUErrorInvalidKey("Invalid key value"); throw MRUError("Invalid key value");
return index->second; return mru[index];
} }
void MRUManager::Add(std::string const& key, agi::fs::path const& entry) { void MRUManager::Add(const char *key, agi::fs::path const& entry) {
MRUListMap &map = Find(key); MRUListMap &map = Find(key);
auto it = find(begin(map), end(map), entry); auto it = find(begin(map), end(map), entry);
if (it == begin(map) && it != end(map)) if (it == begin(map) && it != end(map))
@ -65,20 +93,20 @@ void MRUManager::Add(std::string const& key, agi::fs::path const& entry) {
Flush(); Flush();
} }
void MRUManager::Remove(std::string const& key, agi::fs::path const& entry) { void MRUManager::Remove(const char *key, agi::fs::path const& entry) {
auto& map = Find(key); auto& map = Find(key);
map.erase(remove(begin(map), end(map), entry), end(map)); map.erase(remove(begin(map), end(map), entry), end(map));
Flush(); Flush();
} }
const MRUManager::MRUListMap* MRUManager::Get(std::string const& key) { const MRUManager::MRUListMap* MRUManager::Get(const char *key) {
return &Find(key); return &Find(key);
} }
agi::fs::path const& MRUManager::GetEntry(std::string const& key, const size_t entry) { agi::fs::path const& MRUManager::GetEntry(const char *key, const size_t entry) {
const auto map = Get(key); const auto map = Get(key);
if (entry >= map->size()) if (entry >= map->size())
throw MRUErrorIndexOutOfRange("Requested element index is out of range."); throw MRUError("Requested element index is out of range.");
return *next(map->begin(), entry); return *next(map->begin(), entry);
} }
@ -86,34 +114,38 @@ agi::fs::path const& MRUManager::GetEntry(std::string const& key, const size_t e
void MRUManager::Flush() { void MRUManager::Flush() {
json::Object out; json::Object out;
for (auto const& mru_map : mru) { for (size_t i = 0; i < mru.size(); ++i) {
json::Array &array = out[mru_map.first]; json::Array &array = out[mru_names[i]];
for (auto const& p : mru_map.second) for (auto const& p : mru[i])
array.push_back(p.string()); array.push_back(p.string());
} }
agi::JsonWriter::Write(out, io::Save(config_name).Get()); agi::JsonWriter::Write(out, io::Save(config_name).Get());
} }
void MRUManager::Prune(std::string const& key, MRUListMap& map) const { void MRUManager::Prune(const char *key, MRUListMap& map) const {
size_t limit = 16u; size_t limit = 16u;
if (options) { if (options) {
auto it = option_names.find(key); int idx = mru_index(key);
if (it != option_names.end()) if (idx != -1)
limit = (size_t)options->Get(it->second)->GetInt(); limit = (size_t)options->Get(option_names[idx])->GetInt();
} }
map.resize(std::min(limit, map.size())); map.resize(std::min(limit, map.size()));
} }
void MRUManager::Load(std::string const& key, const json::Array& array) { void MRUManager::Load(const char *key, const json::Array& array) {
int idx = mru_index(key);
if (idx == -1) return;
try { try {
transform(begin(array), end(array), mru[idx].reserve(array.size());
back_inserter(mru[key]), [](std::string const& s) { return s; }); for (std::string const& str : array)
mru[idx].push_back(str);
} }
catch (json::Exception const&) { catch (json::Exception const&) {
// Out of date MRU file; just discard the data and skip it // Out of date MRU file; just discard the data and skip it
} }
Prune(key, mru[key]); Prune(key, mru[idx]);
} }
} }

View file

@ -12,8 +12,8 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include <array>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <map>
#include <vector> #include <vector>
#include <libaegisub/exception.h> #include <libaegisub/exception.h>
@ -28,8 +28,6 @@ namespace agi {
class Options; class Options;
DEFINE_EXCEPTION(MRUError, Exception); DEFINE_EXCEPTION(MRUError, Exception);
DEFINE_EXCEPTION(MRUErrorInvalidKey, MRUError);
DEFINE_EXCEPTION(MRUErrorIndexOutOfRange, MRUError);
/// @class MRUManager /// @class MRUManager
/// @brief Most Recently Used (MRU) list handling /// @brief Most Recently Used (MRU) list handling
@ -54,25 +52,25 @@ public:
/// @brief Add entry to the list. /// @brief Add entry to the list.
/// @param key List name /// @param key List name
/// @param entry Entry to add /// @param entry Entry to add
/// @exception MRUErrorInvalidKey thrown when an invalid key is used. /// @exception MRUError thrown when an invalid key is used.
void Add(std::string const& key, agi::fs::path const& entry); void Add(const char *key, agi::fs::path const& entry);
/// @brief Remove entry from the list. /// @brief Remove entry from the list.
/// @param key List name /// @param key List name
/// @param entry Entry to add /// @param entry Entry to add
/// @exception MRUErrorInvalidKey thrown when an invalid key is used. /// @exception MRUError thrown when an invalid key is used.
void Remove(std::string const& key, agi::fs::path const& entry); void Remove(const char *key, agi::fs::path const& entry);
/// @brief Return list /// @brief Return list
/// @param key List name /// @param key List name
/// @exception MRUErrorInvalidKey thrown when an invalid key is used. /// @exception MRUError thrown when an invalid key is used.
const MRUListMap* Get(std::string const& key); const MRUListMap* Get(const char *key);
/// @brief Return A single entry in a list. /// @brief Return A single entry in a list.
/// @param key List name /// @param key List name
/// @param entry 0-base position of entry /// @param entry 0-base position of entry
/// @exception MRUErrorInvalidKey thrown when an invalid key is used. /// @exception MRUError thrown when an invalid key is used.
agi::fs::path const& GetEntry(std::string const& key, const size_t entry); agi::fs::path const& GetEntry(const char *key, const size_t entry);
/// Write MRU lists to disk. /// Write MRU lists to disk.
void Flush(); void Flush();
@ -84,25 +82,17 @@ private:
/// User preferences object for maximum number of items to list /// User preferences object for maximum number of items to list
agi::Options *const options; agi::Options *const options;
/// @brief Map for MRUListMap values.
/// @param std::string Name
/// @param MRUListMap instance.
using MRUMap = std::map<std::string, MRUListMap>;
/// Internal MRUMap values. /// Internal MRUMap values.
MRUMap mru; std::array<MRUListMap, 7> mru;
/// Map from MRU name to option name
const std::map<std::string, std::string> option_names;
/// @brief Load MRU Lists. /// @brief Load MRU Lists.
/// @param key List name. /// @param key List name.
/// @param array json::Array of values. /// @param array json::Array of values.
void Load(std::string const& key, ::json::Array const& array); void Load(const char *key, ::json::Array const& array);
/// @brief Prune MRUListMap to the desired length. /// @brief Prune MRUListMap to the desired length.
/// This uses the user-set values for MRU list length. /// This uses the user-set values for MRU list length.
void Prune(std::string const& key, MRUListMap& map) const; void Prune(const char *key, MRUListMap& map) const;
MRUListMap &Find(std::string const& key); MRUListMap &Find(const char *key);
}; };
} // namespace agi } // namespace agi

View file

@ -4,7 +4,7 @@
#include <algorithm> #include <algorithm>
wxArrayString lagi_MRU_wxAS(std::string const& list) { wxArrayString lagi_MRU_wxAS(const char *list) {
auto const& vec = *config::mru->Get(list); auto const& vec = *config::mru->Get(list);
wxArrayString ret; wxArrayString ret;
ret.reserve(vec.size()); ret.reserve(vec.size());

View file

@ -16,4 +16,4 @@ wxArrayString to_wx(std::vector<std::string> const& vec);
inline agi::Color from_wx(wxColour color) { return agi::Color(color.Red(), color.Green(), color.Blue(), 255 - color.Alpha()); } inline agi::Color from_wx(wxColour color) { return agi::Color(color.Red(), color.Green(), color.Blue(), 255 - color.Alpha()); }
inline std::string from_wx(wxString const& str) { return std::string(str.utf8_str()); } inline std::string from_wx(wxString const& str) { return std::string(str.utf8_str()); }
wxArrayString lagi_MRU_wxAS(std::string const& list); wxArrayString lagi_MRU_wxAS(const char *list);

View file

@ -84,7 +84,7 @@ public:
} }
void Update() { void Update() {
const auto mru = config::mru->Get(type); const auto mru = config::mru->Get(type.c_str());
Resize(mru->size()); Resize(mru->size());

View file

@ -20,9 +20,9 @@ icacls data\dir_access_denied /deny %USERNAME%:F
mkdir data\dir_read_only mkdir data\dir_read_only
icacls data\dir_read_only /deny %USERNAME%:W icacls data\dir_read_only /deny %USERNAME%:W
echo {"Valid" : ["Entry One", "Entry Two"]} > data/mru_ok.json echo {"Video" : ["Entry One", "Entry Two"]} > data/mru_ok.json
echo {"Invalid" : [1, 3]} > data/mru_invalid.json echo {"Video" : [1, 3]} > data/mru_invalid.json
echo '1234567890' > data\ten_bytes echo '1234567890' > data\ten_bytes
echo '' > data\touch_mod_time echo '' > data\touch_mod_time

View file

@ -23,9 +23,9 @@ chmod 000 data/dir_access_denied
mkdir data/dir_read_only mkdir data/dir_read_only
chmod 444 data/dir_read_only chmod 444 data/dir_read_only
echo '{"Valid" : ["Entry One", "Entry Two"]}' > data/mru_ok.json echo '{"Video" : ["Entry One", "Entry Two"]}' > data/mru_ok.json
echo '{"Invalid" : [1, 3]}' > data/mru_invalid.json echo '{"Video" : [1, 3]}' > data/mru_invalid.json
printf %s '1234567890' > data/ten_bytes printf %s '1234567890' > data/ten_bytes
touch -r $0 data/touch_mod_time touch -r $0 data/touch_mod_time

View file

@ -12,99 +12,129 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include <libaegisub/fs.h>
#include <libaegisub/mru.h> #include <libaegisub/mru.h>
#include <libaegisub/fs.h>
#include <libaegisub/option.h>
#include <libaegisub/option_value.h>
#include <main.h> #include <main.h>
static const char default_mru[] = "{\"Valid\" : []}"; static const char default_mru[] = "{\"Video\" : []}";
static const char conf_ok[] = "data/mru_ok.json";
class lagi_mru : public libagi { TEST(lagi_mru, load_from_file) {
protected:
std::string conf_ok;
void SetUp() override {
conf_ok = "./data/mru_ok.json";
}
};
TEST_F(lagi_mru, load_from_file) {
ASSERT_NO_THROW(agi::MRUManager mru(conf_ok, default_mru)); ASSERT_NO_THROW(agi::MRUManager mru(conf_ok, default_mru));
agi::MRUManager mru(conf_ok, default_mru); agi::MRUManager mru(conf_ok, default_mru);
ASSERT_NO_THROW(mru.Get("Valid")); ASSERT_NO_THROW(mru.Get("Video"));
ASSERT_EQ(2u, mru.Get("Valid")->size()); ASSERT_EQ(2u, mru.Get("Video")->size());
auto entry = mru.Get("Valid")->begin(); auto entry = mru.Get("Video")->begin();
EXPECT_STREQ("Entry One", (*entry++).string().c_str()); EXPECT_STREQ("Entry One", (*entry++).string().c_str());
EXPECT_STREQ("Entry Two", (*entry++).string().c_str()); EXPECT_STREQ("Entry Two", (*entry++).string().c_str());
EXPECT_TRUE(mru.Get("Valid")->end() == entry); EXPECT_TRUE(mru.Get("Video")->end() == entry);
} }
TEST_F(lagi_mru, load_from_default_string) { TEST(lagi_mru, load_from_default_string) {
agi::fs::Remove("data/mru_tmp"); agi::fs::Remove("data/mru_tmp");
agi::MRUManager mru("data/mru_tmp", default_mru); agi::MRUManager mru("data/mru_tmp", default_mru);
} }
TEST_F(lagi_mru, load_from_invalid_file) { TEST(lagi_mru, load_from_invalid_file) {
agi::fs::Copy("data/mru_invalid.json", "data/mru_tmp"); agi::fs::Copy("data/mru_invalid.json", "data/mru_tmp");
agi::MRUManager mru("data/mru_tmp", default_mru); agi::MRUManager mru("data/mru_tmp", default_mru);
EXPECT_TRUE(mru.Get("Invalid")->empty()); EXPECT_TRUE(mru.Get("Video")->empty());
} }
TEST_F(lagi_mru, add_entry) { TEST(lagi_mru, add_entry) {
agi::fs::Copy("data/mru_ok.json", "data/mru_tmp"); agi::fs::Copy("data/mru_ok.json", "data/mru_tmp");
agi::MRUManager mru("data/mru_tmp", default_mru); agi::MRUManager mru("data/mru_tmp", default_mru);
EXPECT_NO_THROW(mru.Add("Valid", "/path/to/file")); EXPECT_NO_THROW(mru.Add("Video", "/path/to/file"));
EXPECT_STREQ("/path/to/file", mru.Get("Valid")->front().string().c_str()); EXPECT_STREQ("/path/to/file", mru.Get("Video")->front().string().c_str());
} }
TEST_F(lagi_mru, remove_entry) { TEST(lagi_mru, remove_entry) {
agi::fs::Copy("data/mru_ok.json", "data/mru_tmp"); agi::fs::Copy("data/mru_ok.json", "data/mru_tmp");
agi::MRUManager mru("data/mru_tmp", default_mru); agi::MRUManager mru("data/mru_tmp", default_mru);
EXPECT_NO_THROW(mru.Add("Valid", "/path/to/file")); EXPECT_NO_THROW(mru.Add("Video", "/path/to/file"));
EXPECT_NO_THROW(mru.Remove("Valid", "/path/to/file")); EXPECT_NO_THROW(mru.Remove("Video", "/path/to/file"));
EXPECT_STRNE("/path/to/file", mru.Get("Valid")->front().string().c_str()); EXPECT_STRNE("/path/to/file", mru.Get("Video")->front().string().c_str());
} }
TEST_F(lagi_mru, invalid_mru_key_throws) { TEST(lagi_mru, invalid_mru_key_throws) {
agi::fs::Copy("data/mru_ok.json", "data/mru_tmp"); agi::fs::Copy("data/mru_ok.json", "data/mru_tmp");
agi::MRUManager mru("data/mru_tmp", default_mru); agi::MRUManager mru("data/mru_tmp", default_mru);
EXPECT_THROW(mru.Add("Invalid", "/path/to/file"), agi::MRUErrorInvalidKey); EXPECT_THROW(mru.Add("Invalid", "/path/to/file"), agi::MRUError);
EXPECT_THROW(mru.Get("Invalid"), agi::MRUErrorInvalidKey); EXPECT_THROW(mru.Get("Invalid"), agi::MRUError);
} }
TEST_F(lagi_mru, valid_mru_key_doesnt_throw) { TEST(lagi_mru, valid_mru_key_doesnt_throw) {
agi::fs::Copy("data/mru_ok.json", "data/mru_tmp"); agi::fs::Copy("data/mru_ok.json", "data/mru_tmp");
agi::MRUManager mru("data/mru_tmp", default_mru); agi::MRUManager mru("data/mru_tmp", default_mru);
EXPECT_NO_THROW(mru.Add("Valid", "/path/to/file")); EXPECT_NO_THROW(mru.Add("Video", "/path/to/file"));
} }
TEST_F(lagi_mru, adding_existing_moves_to_front) { TEST(lagi_mru, adding_existing_moves_to_front) {
agi::fs::Remove("data/mru_tmp"); agi::fs::Remove("data/mru_tmp");
agi::MRUManager mru("data/mru_tmp", default_mru); agi::MRUManager mru("data/mru_tmp", default_mru);
EXPECT_NO_THROW(mru.Add("Valid", "/file/1")); EXPECT_NO_THROW(mru.Add("Video", "/file/1"));
EXPECT_NO_THROW(mru.Add("Valid", "/file/2")); EXPECT_NO_THROW(mru.Add("Video", "/file/2"));
EXPECT_NO_THROW(mru.Add("Valid", "/file/3")); EXPECT_NO_THROW(mru.Add("Video", "/file/3"));
EXPECT_NO_THROW(mru.Add("Valid", "/file/1")); EXPECT_NO_THROW(mru.Add("Video", "/file/1"));
EXPECT_NO_THROW(mru.Add("Valid", "/file/1")); EXPECT_NO_THROW(mru.Add("Video", "/file/1"));
EXPECT_NO_THROW(mru.Add("Valid", "/file/3")); EXPECT_NO_THROW(mru.Add("Video", "/file/3"));
EXPECT_STREQ("/file/3", mru.GetEntry("Valid", 0).string().c_str()); EXPECT_STREQ("/file/3", mru.GetEntry("Video", 0).string().c_str());
EXPECT_STREQ("/file/1", mru.GetEntry("Valid", 1).string().c_str()); EXPECT_STREQ("/file/1", mru.GetEntry("Video", 1).string().c_str());
EXPECT_STREQ("/file/2", mru.GetEntry("Valid", 2).string().c_str()); EXPECT_STREQ("/file/2", mru.GetEntry("Video", 2).string().c_str());
EXPECT_THROW(mru.GetEntry("Valid", 3), agi::MRUErrorIndexOutOfRange); EXPECT_THROW(mru.GetEntry("Video", 3), agi::MRUError);
}
TEST(lagi_mru, all_valid_keys_work) {
agi::fs::Remove("data/mru_tmp");
agi::MRUManager mru("data/mru_tmp", default_mru);
EXPECT_NO_THROW(mru.Add("Audio", "/file"));
EXPECT_NO_THROW(mru.Add("Find", "/file"));
EXPECT_NO_THROW(mru.Add("Keyframes", "/file"));
EXPECT_NO_THROW(mru.Add("Replace", "/file"));
EXPECT_NO_THROW(mru.Add("Subtitle", "/file"));
EXPECT_NO_THROW(mru.Add("Timecodes", "/file"));
EXPECT_NO_THROW(mru.Add("Video", "/file"));
}
TEST(lagi_mru, prune_works) {
agi::fs::Remove("data/mru_tmp");
agi::MRUManager mru("data/mru_tmp", default_mru);
for (int i = 0; i < 20; ++i) {
ASSERT_NO_THROW(mru.Add("Audio", std::to_string(i)));
}
EXPECT_EQ(16, mru.Get("Audio")->size());
EXPECT_EQ("19", mru.Get("Audio")->front());
}
TEST(lagi_mru, prune_obeys_option) {
static const char opt[] = R"raw({"Limits": {"MRU": 1}})raw";
agi::Options options("", opt, agi::Options::FLUSH_SKIP);
agi::fs::Remove("data/mru_tmp");
agi::MRUManager mru("data/mru_tmp", {default_mru, sizeof(default_mru)}, &options);
ASSERT_NO_THROW(mru.Add("Audio", "1"));
ASSERT_NO_THROW(mru.Add("Audio", "2"));
EXPECT_EQ(1, mru.Get("Audio")->size());
} }
// Check to make sure an entry is really removed. This was fixed in // Check to make sure an entry is really removed. This was fixed in
// r4347, the entry was being removed from a copy of the map internally. // r4347, the entry was being removed from a copy of the map internally.
TEST_F(lagi_mru, MRUEntryRemove_r4347) { TEST(lagi_mru, MRUEntryRemove_r4347) {
agi::MRUManager mru(conf_ok, default_mru); agi::MRUManager mru(conf_ok, default_mru);
EXPECT_NO_THROW(mru.Add("Valid", "/path/to/file")); EXPECT_NO_THROW(mru.Add("Video", "/path/to/file"));
EXPECT_NO_THROW(mru.Remove("Valid", "/path/to/file")); EXPECT_NO_THROW(mru.Remove("Video", "/path/to/file"));
const agi::MRUManager::MRUListMap *map_list = mru.Get("Valid"); const agi::MRUManager::MRUListMap *map_list = mru.Get("Video");
auto i_lst = map_list->begin(); auto i_lst = map_list->begin();
if ((i_lst != map_list->end()) && (*i_lst == "/path/to/file")) if ((i_lst != map_list->end()) && (*i_lst == "/path/to/file"))