diff --git a/src/Storage.cpp b/src/Storage.cpp index a0b8e12873..88039d12f0 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -21,6 +21,99 @@ Storage::Storage() {} Storage::~Storage() {} +std::shared_ptr Storage::GetEntryValue(StringRef name) const { + std::lock_guard lock(m_mutex); + auto i = m_entries.find(name); + if (i == m_entries.end()) + return nullptr; + return i->getValue().value; +} + +bool Storage::SetEntryValue(StringRef name, std::shared_ptr value) { + if (name.empty()) return true; + if (!value) return true; + std::lock_guard lock(m_mutex); + auto& entry = m_entries[name]; + if (entry.value && entry.value->type() != value->type()) + return false; // error on type mismatch + if (!entry.value || *entry.value != *value) + m_updates.push(Update{name, Update::kValueUpdate}); // put on update queue + entry.value = value; + return true; +} + +void Storage::SetEntryTypeValue(StringRef name, std::shared_ptr value) { + if (name.empty()) return; + if (!value) return; + std::lock_guard lock(m_mutex); + auto& entry = m_entries[name]; + if (!entry.value || *entry.value != *value) { + // put on update queue + if (!entry.value || entry.value->type() != value->type()) + m_updates.push(Update{name, Update::kAssign}); + else + m_updates.push(Update{name, Update::kValueUpdate}); + } + entry.value = value; +} + +void Storage::SetEntryFlags(StringRef name, unsigned int flags) { + std::lock_guard lock(m_mutex); + auto i = m_entries.find(name); + if (i == m_entries.end()) + return; + auto& entry = i->getValue(); + if (entry.flags != flags) + m_updates.push(Update{name, Update::kFlagsUpdate}); // put on update queue + entry.flags = flags; +} + +unsigned int Storage::GetEntryFlags(StringRef name) const { + std::lock_guard lock(m_mutex); + auto i = m_entries.find(name); + if (i == m_entries.end()) + return 0; + return i->getValue().flags; +} + +void Storage::DeleteEntry(StringRef name) { + std::lock_guard lock(m_mutex); + auto i = m_entries.find(name); + if (i == m_entries.end()) + return; + auto& entry = i->getValue(); + if (entry.value) + m_updates.push(Update{name, Update::kDelete}); // put on update queue + entry.value = nullptr; +} + +void Storage::DeleteAllEntries() { + std::lock_guard lock(m_mutex); + for (auto& i : m_entries) { + auto& entry = i.getValue(); + if (entry.value) entry.value = nullptr; + } + m_updates.push(Update{"", Update::kDeleteAll}); // put on update queue +} + +std::vector Storage::GetEntryInfo(StringRef prefix, + unsigned int types) { + std::lock_guard lock(m_mutex); + std::vector infos; + for (auto& i : m_entries) { + if (!i.getKey().startswith(prefix)) continue; + auto& entry = i.getValue(); + if (!entry.value) continue; + EntryInfo info; + info.name = i.getKey(); + info.type = entry.value->type(); + info.flags = entry.flags; + info.last_change = entry.value->last_change(); + infos.push_back(std::move(info)); + } + return infos; +} + /* Escapes and writes a string, including start and end double quotes */ static void WriteString(std::ostream& os, llvm::StringRef str) { os << '"'; @@ -54,18 +147,27 @@ static void WriteString(std::ostream& os, llvm::StringRef str) { } void Storage::SavePersistent(std::ostream& os) const { + // copy values out of storage as quickly as possible so lock isn't held + std::vector>> entries; + { + std::lock_guard lock(m_mutex); + entries.reserve(m_entries.size()); + for (auto& i : m_entries) { + const StorageEntry& entry = i.getValue(); + // only write persistent-flagged values + if (!entry.IsPersistent()) continue; + entries.push_back(std::make_pair(i.getKey(), entry.value)); + } + } + std::string base64_encoded; // header os << "[NetworkTables Storage 3.0]\n"; - for (auto& i : m_entries) { - const StorageEntry& entry = i.getValue(); - // only write persistent-flagged values - if (!entry.IsPersistent()) continue; - + for (auto& i : entries) { // type - auto v = entry.value; + auto v = i.second; if (!v) continue; switch (v->type()) { case NT_BOOLEAN: @@ -94,7 +196,7 @@ void Storage::SavePersistent(std::ostream& os) const { } // name - WriteString(os, i.getKey()); + WriteString(os, i.first); // = os << '='; @@ -239,6 +341,9 @@ bool Storage::LoadPersistent( std::string line_str; std::size_t line_num = 1; + // entries to add + std::vector>> entries; + // declare these outside the loop to reduce reallocs std::string name, str; std::vector boolean_array; @@ -308,14 +413,14 @@ bool Storage::LoadPersistent( line = line.drop_front().ltrim(" \t"); // value - StorageEntry entry; + std::shared_ptr value; switch (type) { case NT_BOOLEAN: // only true or false is accepted if (line == "true") - entry.value = Value::MakeBoolean(true); + value = Value::MakeBoolean(true); else if (line == "false") - entry.value = Value::MakeBoolean(false); + value = Value::MakeBoolean(false); else { if (warn) warn(line_num, "unrecognized boolean value, not 'true' or 'false'"); @@ -332,7 +437,7 @@ bool Storage::LoadPersistent( if (warn) warn(line_num, "invalid double value"); goto next_line; } - entry.value = Value::MakeDouble(v); + value = Value::MakeDouble(v); break; } case NT_STRING: { @@ -347,12 +452,12 @@ bool Storage::LoadPersistent( goto next_line; } UnescapeString(str_tok, &str); - entry.value = Value::MakeString(std::move(str)); + value = Value::MakeString(std::move(str)); break; } case NT_RAW: Base64Decode(line, &str); - entry.value = Value::MakeRaw(std::move(str)); + value = Value::MakeRaw(std::move(str)); break; case NT_BOOLEAN_ARRAY: { llvm::StringRef elem_tok; @@ -372,7 +477,7 @@ bool Storage::LoadPersistent( } } - entry.value = Value::MakeBooleanArray(std::move(boolean_array)); + value = Value::MakeBooleanArray(std::move(boolean_array)); break; } case NT_DOUBLE_ARRAY: { @@ -393,7 +498,7 @@ bool Storage::LoadPersistent( double_array.push_back(v); } - entry.value = Value::MakeDoubleArray(std::move(double_array)); + value = Value::MakeDoubleArray(std::move(double_array)); break; } case NT_STRING_ARRAY: { @@ -421,14 +526,36 @@ bool Storage::LoadPersistent( string_array.push_back(std::move(str)); } - entry.value = Value::MakeStringArray(std::move(string_array)); + value = Value::MakeStringArray(std::move(string_array)); break; } default: break; } + if (!name.empty() && value) + entries.push_back(std::make_pair(std::move(name), std::move(value))); next_line: ; } + + // copy values into storage as quickly as possible so lock isn't held + { + std::lock_guard lock(m_mutex); + for (auto& i : entries) { + auto& entry = m_entries[i.first]; + + // put on update queue + if (!entry.value || entry.value->type() != i.second->type()) + m_updates.push(Update{i.first, Update::kAssign}); + else if (*entry.value != *i.second) + m_updates.push(Update{i.first, Update::kValueUpdate}); + if (!entry.IsPersistent()) + m_updates.push(Update{std::move(i.first), Update::kFlagsUpdate}); + + entry.value = i.second; + entry.flags |= NT_PERSISTENT; + } + } + return true; } diff --git a/src/Storage.h b/src/Storage.h index baa383d06a..8a7ec4cdf3 100644 --- a/src/Storage.h +++ b/src/Storage.h @@ -12,9 +12,11 @@ #include #include #include +#include #include "llvm/StringMap.h" -#include "nt_Value.h" +#include "support/ConcurrentQueue.h" +#include "ntcore_cpp.h" #include "SequenceNumber.h" namespace nt { @@ -41,9 +43,28 @@ class Storage { typedef llvm::StringMap EntriesMap; + struct Update { + std::string name; + enum Kind { kAssign, kValueUpdate, kFlagsUpdate, kDelete, kDeleteAll }; + Kind kind; + }; + typedef ConcurrentQueue UpdateQueue; + + std::mutex& mutex() { return m_mutex; } EntriesMap& entries() { return m_entries; } const EntriesMap& entries() const { return m_entries; } + UpdateQueue& updates() { return m_updates; } + + std::shared_ptr GetEntryValue(StringRef name) const; + bool SetEntryValue(StringRef name, std::shared_ptr value); + void SetEntryTypeValue(StringRef name, std::shared_ptr value); + void SetEntryFlags(StringRef name, unsigned int flags); + unsigned int GetEntryFlags(StringRef name) const; + void DeleteEntry(StringRef name); + void DeleteAllEntries(); + std::vector GetEntryInfo(StringRef prefix, unsigned int types); + void SavePersistent(std::ostream& os) const; bool LoadPersistent( std::istream& is, @@ -54,7 +75,9 @@ class Storage { Storage(const Storage&) = delete; Storage& operator=(const Storage&) = delete; + mutable std::mutex m_mutex; EntriesMap m_entries; + UpdateQueue m_updates; static std::unique_ptr m_instance; }; diff --git a/src/ntcore_cpp.cpp b/src/ntcore_cpp.cpp index eccc3daf32..47fa3c8118 100644 --- a/src/ntcore_cpp.cpp +++ b/src/ntcore_cpp.cpp @@ -22,27 +22,35 @@ namespace nt { */ std::shared_ptr GetEntryValue(StringRef name) { - return nullptr; + return Storage::GetInstance().GetEntryValue(name); } bool SetEntryValue(StringRef name, std::shared_ptr value) { - return false; + return Storage::GetInstance().SetEntryValue(name, value); } -void SetEntryTypeValue(StringRef name, std::shared_ptr value) {} +void SetEntryTypeValue(StringRef name, std::shared_ptr value) { + Storage::GetInstance().SetEntryTypeValue(name, value); +} -void SetEntryFlags(StringRef name, unsigned int flags) {} +void SetEntryFlags(StringRef name, unsigned int flags) { + Storage::GetInstance().SetEntryFlags(name, flags); +} unsigned int GetEntryFlags(StringRef name) { - return 0; + return Storage::GetInstance().GetEntryFlags(name); } -void DeleteEntry(StringRef name) {} +void DeleteEntry(StringRef name) { + Storage::GetInstance().DeleteEntry(name); +} -void DeleteAllEntries() {} +void DeleteAllEntries() { + Storage::GetInstance().DeleteAllEntries(); +} std::vector GetEntryInfo(StringRef prefix, unsigned int types) { - return std::vector(); + return Storage::GetInstance().GetEntryInfo(prefix, types); } void Flush() {