diff --git a/inc/osvr/Common/RegisteredStringMap.h b/inc/osvr/Common/RegisteredStringMap.h new file mode 100644 index 000000000..5ef51d620 --- /dev/null +++ b/inc/osvr/Common/RegisteredStringMap.h @@ -0,0 +1,99 @@ +/** @file + @brief Header + + @date 2015 + + @author + Sensics, Inc. + +*/ + +// Copyright 2015 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef INCLUDED_RegisteredStringMap_h_GUID_066235BE_3687_44AD_C2A4_593D6E6780F3 +#define INCLUDED_RegisteredStringMap_h_GUID_066235BE_3687_44AD_C2A4_593D6E6780F3 + +// Internal Includes +#include +#include + +// Library/third-party includes +#include + +// Standard includes +#include + +namespace osvr { +namespace common { + + /// Centralize a string registry. Basically, the server side, and part + /// of the client side internals. + class RegisteredStringMap { + public: + /// retrieve the ID for the current name or register new ID and return + /// that + OSVR_COMMON_EXPORT util::StringID getStringID(std::string const &str); + + /// retrieve the name of the string given the ID + /// returns empty string if nothing found + OSVR_COMMON_EXPORT std::string getStringFromId(util::StringID id) const; + + /// Has a new entry been added since the flag was last cleared? + OSVR_COMMON_EXPORT bool isModified() const; + /// Clear the modified flag + OSVR_COMMON_EXPORT void clearModifiedFlag(); + + OSVR_COMMON_EXPORT void printCurrentMap(); + + OSVR_COMMON_EXPORT std::vector getEntries() const; + + protected: + std::vector m_regEntries; + + /// special flag that gets switched whenever new element is inserted; + bool m_modified = false; + }; + + /// This is like a RegisteredStringMap, except it also knows that some peer + /// also has a string map, likely with some of the same strings, but with + /// different ids. Used in reconciliation between server and client since + /// they are separate entities + class CorrelatedStringMap { + public: + /// retrieve the ID for the current name or register new ID and return + /// that + OSVR_COMMON_EXPORT util::StringID getStringID(std::string const &str); + + /// retrieve the name of the string given the ID + /// returns empty string if nothing found + OSVR_COMMON_EXPORT std::string getStringFromId(util::StringID id) const; + + /// This is the extra method used by clients, to convert from server's + /// ids. Will return NULL if peerID to Local ID mapping doesn't exist + OSVR_COMMON_EXPORT util::StringID + convertPeerToLocalID(util::PeerStringID peerID) const; + + /// This populates the data structure used by the above method. + OSVR_COMMON_EXPORT void + setupPeerMappings(std::vector const &peerEntries); + + private: + RegisteredStringMap m_local; + /// keeps the peer to local string ID mappings + std::vector m_remoteToLocal; + }; +} // namespace common +} // namespace osvr +#endif // INCLUDED_RegisteredStringMap_h_GUID_066235BE_3687_44AD_C2A4_593D6E6780F3 diff --git a/inc/osvr/Common/SerializationTraits.h b/inc/osvr/Common/SerializationTraits.h index 34c6999f6..943783611 100644 --- a/inc/osvr/Common/SerializationTraits.h +++ b/inc/osvr/Common/SerializationTraits.h @@ -32,6 +32,7 @@ #include #include #include +#include // Library/third-party includes #include @@ -39,6 +40,7 @@ // Standard includes #include +#include #include namespace osvr { @@ -492,6 +494,51 @@ namespace common { } }; + template + struct SerializationTraits< + DefaultSerializationTag>, void> + : BaseSerializationTraits> { + using value_type = std::vector; + using param_type = value_type const &; + using reference_type = value_type &; + typedef BaseSerializationTraits> Base; + typedef DefaultSerializationTag> tag_type; + + template + static void serialize(BufferType &buf, param_type val, + tag_type const &) { + serializeRaw(buf, static_cast(val.size())); + for (auto &elt : val) { + serializeRaw(buf, elt); + } + } + + template + static void deserialize(BufferReaderType &buf, reference_type val, + tag_type const &) { + uint32_t n; + deserializeRaw(buf, n); + val.clear(); + val.reserve(n); + for (uint32_t i = 0; i < n; ++i) { + ValueType elt; + deserializeRaw(buf, elt); + val.push_back(elt); + } + } + + static size_t spaceRequired(size_t existingBytes, param_type val, + tag_type const &) { + size_t bytes = existingBytes; + bytes += getBufferSpaceRequiredRaw( + bytes, static_cast(val.size())); + for (auto &elt : val) { + bytes += getBufferSpaceRequiredRaw(bytes, elt); + } + return bytes - existingBytes; + } + }; + template <> struct SimpleStructSerialization : SimpleStructSerializationBase { @@ -510,6 +557,15 @@ namespace common { f(val.data[2]); } }; + + template + struct SimpleStructSerialization> + : SimpleStructSerializationBase { + template static void apply(F &f, T &val) { + f(val.value()); + } + }; + } // namespace serialization } // namespace common diff --git a/inc/osvr/Util/StringIds.h b/inc/osvr/Util/StringIds.h new file mode 100644 index 000000000..6c961f6c1 --- /dev/null +++ b/inc/osvr/Util/StringIds.h @@ -0,0 +1,46 @@ +/** @file + @brief Header + + @date 2015 + + @author + Sensics, Inc. + +*/ + +// Copyright 2015 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef INCLUDED_StringIds_h_GUID_080FD2D7_0F0B_438B_E71E_7D2836C8921B +#define INCLUDED_StringIds_h_GUID_080FD2D7_0F0B_438B_E71E_7D2836C8921B + +// Internal Includes +#include + +// Library/third-party includes +// - none + +// Standard includes +// - none + +namespace osvr { +namespace util { + struct LocalStringIdTag; + struct PeerStringIdTag; + typedef TypeSafeId StringID; + typedef TypeSafeId PeerStringID; +} // namespace util +} // namespace osvr + +#endif // INCLUDED_StringIds_h_GUID_080FD2D7_0F0B_438B_E71E_7D2836C8921B diff --git a/inc/osvr/Util/TypeSafeId.h b/inc/osvr/Util/TypeSafeId.h new file mode 100644 index 000000000..3f750abb5 --- /dev/null +++ b/inc/osvr/Util/TypeSafeId.h @@ -0,0 +1,124 @@ +/** @file + @brief Header + + @date 2015 + + @author + Sensics, Inc. + +*/ + +// Copyright 2015 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef INCLUDED_TypeSafeId_h_GUID_137CA336_382A_4796_7735_4521F02D5AC2 +#define INCLUDED_TypeSafeId_h_GUID_137CA336_382A_4796_7735_4521F02D5AC2 + +// Internal Includes +#include + +// Library/third-party includes +// - none + +// Standard includes +#include + +namespace osvr { +namespace util { + /// @brief Namespace for traits templates associated with + /// ::osvr::util::TypeSafeId + namespace typesafeid_traits { + /// @brief Explicitly specialize for your tag type if you want a + /// different + /// underlying type. + template struct WrappedType { typedef uint32_t type; }; + + /// @brief Explicitly specialize for your tag type if you want a + /// different signal value for invalid/empty: default is the maximum + /// representable value for your type. + template struct SentinelValue { + typedef typename WrappedType::type wrapped_type; + static wrapped_type get() { + return std::numeric_limits::max(); + } + }; + + } // namespace typesafeid_traits + + /// @brief A generic typesafe (as long as you use differing tag types) + /// wrapper for identifiers (typically integers). + /// + /// @tparam Tag any type - does not have to be defined, just declared (so + /// `struct mytag;` somewhere is fine). The tag serves to make integer IDs + /// have distinct types, and also serves as a look-up key in the + /// ::osvr::util::typesafeid_traits classes for underlying integer type and + /// sentinel empty/invalid value. + /// + /// Initial implementation inspired by + /// http://www.ilikebigbits.com/blog/2014/5/6/type-safe-identifiers-in-c + /// though this version now strays quite far by strengthening type-safety + /// and encapsulation, and by using traits classes to specify details based + /// on tag type alone. + template class TypeSafeId { + public: + /// @brief The type of the current class. + typedef TypeSafeId type; + + /// @brief The contained/wrapped type. + typedef typename typesafeid_traits::WrappedType::type wrapped_type; + + /// @brief Static factory method to return an invalid/empty ID. + static type invalid() { return type(); } + + // Default constructor which will set m_val to the empty/invalid value. + TypeSafeId() : m_val(sentinel()) {} + + // Explicit constructor from the wrapped type + explicit TypeSafeId(wrapped_type val) : m_val(val) {} + + /// @brief Check whether the ID is empty/invalid + bool empty() const { return m_val == sentinel(); } + + /// @brief Read-only accessor to the (non-type-safe!) wrapped value + wrapped_type value() const { return m_val; } + + /// @brief Reference accessor to the (non-type-safe!) wrapped value + wrapped_type & value() { return m_val; } + + private: + /// @brief Utility function to access the SentinelValue trait. + static wrapped_type sentinel() { + return typesafeid_traits::SentinelValue::get(); + } + wrapped_type m_val; + }; + + /// @brief Equality comparison operator for type-safe IDs + /// @relates ::osvr::util::TypeSafeId + template + inline bool operator==(TypeSafeId const a, TypeSafeId const b) { + return a.value() == b.value(); + } + + /// @brief Inequality comparison operator for type-safe IDs + /// @relates ::osvr::util::TypeSafeId + template + inline bool operator!=(TypeSafeId const a, TypeSafeId const b) { + return a.value() != b.value(); + } + +} // namespace util +} // namespace osvr + +#endif // INCLUDED_TypeSafeId_h_GUID_137CA336_382A_4796_7735_4521F02D5AC2 diff --git a/src/osvr/Common/CMakeLists.txt b/src/osvr/Common/CMakeLists.txt index e5621df77..574134afe 100644 --- a/src/osvr/Common/CMakeLists.txt +++ b/src/osvr/Common/CMakeLists.txt @@ -87,6 +87,7 @@ set(API "${HEADER_LOCATION}/ProcessDeviceDescriptor.h" "${HEADER_LOCATION}/RawMessageType.h" "${HEADER_LOCATION}/RawSenderType.h" + "${HEADER_LOCATION}/RegisteredStringMap.h" "${HEADER_LOCATION}/ReportFromCallback.h" "${HEADER_LOCATION}/ReportMap.h" "${HEADER_LOCATION}/ReportState.h" @@ -152,6 +153,7 @@ set(SOURCE ProcessDeviceDescriptor.cpp RawMessageType.cpp RawSenderType.cpp + RegisteredStringMap.cpp ResolveFullTree.cpp ResolveTreeNode.cpp RouteContainer.cpp diff --git a/src/osvr/Common/RegisteredStringMap.cpp b/src/osvr/Common/RegisteredStringMap.cpp new file mode 100644 index 000000000..ee416226d --- /dev/null +++ b/src/osvr/Common/RegisteredStringMap.cpp @@ -0,0 +1,111 @@ +/** @file + @brief Implementation + + @date 2015 + + @author + Sensics, Inc. + + +*/ + +// Copyright 2015 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Internal Includes +#include + +// Library/third-party includes +#include + +// Standard includes +#include + +namespace osvr { +namespace common { + + /// @brief helper function to print size and contents of the map + void RegisteredStringMap::printCurrentMap() { + auto n = m_regEntries.size(); + std::cout << "Current map contains " << m_regEntries.size() + << " entries: " << std::endl; + for (decltype(n) i = 0; i < n; ++i) { + std::cout << "ID: " << i << "; " + << "Name: " << m_regEntries[i] << std::endl; + } + } + + util::StringID RegisteredStringMap::getStringID(std::string const &str) { + auto entry = std::find(begin(m_regEntries), end(m_regEntries), str); + if (end(m_regEntries) != entry) { + // we found it. + return util::StringID(std::distance(begin(m_regEntries), entry)); + } + + // we didn't find an entry in the registry so we'll add a new one + auto ret = util::StringID( + m_regEntries.size()); // will be the location of the next insert. + m_regEntries.push_back(str); + m_modified = true; + return ret; + } + + std::string RegisteredStringMap::getStringFromId(util::StringID id) const { + + // requested non-existent ID (include sanity check) + if (id.value() >= m_regEntries.size()) { + // returning empty string + /// @todo should we throw here? + return std::string(); + } + + return m_regEntries[id.value()]; + }; + + bool RegisteredStringMap::isModified() const { return m_modified; } + void RegisteredStringMap::clearModifiedFlag() { m_modified = false; } + std::vector RegisteredStringMap::getEntries() const { + return m_regEntries; + } + + util::StringID CorrelatedStringMap::getStringID(std::string const &str) { + return m_local.getStringID(str); + } + + std::string CorrelatedStringMap::getStringFromId(util::StringID id) const { + return m_local.getStringFromId(id); + } + + util::StringID + CorrelatedStringMap::convertPeerToLocalID(util::PeerStringID peerID) const { + if (peerID.empty()) { + return util::StringID(); + } + if (peerID.value() >= m_remoteToLocal.size()) { + throw std::out_of_range("Peer ID out of range!"); + } + return util::StringID(m_remoteToLocal[peerID.value()]); + } + + void CorrelatedStringMap::setupPeerMappings( + std::vector const &peerEntries) { + m_remoteToLocal.clear(); + auto n = peerEntries.size(); + for (uint32_t i = 0; i < n; ++i) { + m_remoteToLocal.push_back( + m_local.getStringID(peerEntries[i]).value()); + } + } +} +} diff --git a/src/osvr/Util/CMakeLists.txt b/src/osvr/Util/CMakeLists.txt index f61f9f206..565c40614 100644 --- a/src/osvr/Util/CMakeLists.txt +++ b/src/osvr/Util/CMakeLists.txt @@ -80,6 +80,7 @@ set(API "${HEADER_LOCATION}/StdInt.h" "${HEADER_LOCATION}/StringBufferBuilder.h" "${HEADER_LOCATION}/StringLiteralFileToString.h" + "${HEADER_LOCATION}/StringIds.h" "${HEADER_LOCATION}/TimeValue.h" "${HEADER_LOCATION}/TimeValueC.h" "${HEADER_LOCATION}/TimeValue_fwd.h" @@ -87,6 +88,7 @@ set(API "${HEADER_LOCATION}/TreeNode_fwd.h" "${HEADER_LOCATION}/TreeTraversalVisitor.h" "${HEADER_LOCATION}/TypePack.h" + "${HEADER_LOCATION}/TypeSafeId.h" "${HEADER_LOCATION}/UniquePtr.h" "${HEADER_LOCATION}/UniqueContainer.h" "${HEADER_LOCATION}/Util.h" diff --git a/tests/cplusplus/Common/CMakeLists.txt b/tests/cplusplus/Common/CMakeLists.txt index a5b0bb854..9adee405a 100644 --- a/tests/cplusplus/Common/CMakeLists.txt +++ b/tests/cplusplus/Common/CMakeLists.txt @@ -15,6 +15,7 @@ endif() add_executable(TestCommon DummyTree.h PathTreeResolution.cpp + RegStringMap.cpp Serialization.cpp SerializationExamples.cpp "${PROJECT_SOURCE_DIR}/examples/internals/SerializationTraitExample_Simple.h" diff --git a/tests/cplusplus/Common/RegStringMap.cpp b/tests/cplusplus/Common/RegStringMap.cpp new file mode 100644 index 000000000..044eadf9a --- /dev/null +++ b/tests/cplusplus/Common/RegStringMap.cpp @@ -0,0 +1,152 @@ +/** @file + @brief Implementation + + @date 2015 + + @author + Sensics, Inc. + +*/ + +// Copyright 2015 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Internal Includes +#include + +// Library/third-party includes +#include "gtest/gtest.h" + +// Standard includes +#include + +using osvr::util::StringID; +using osvr::util::PeerStringID; +using osvr::common::RegisteredStringMap; +using osvr::common::CorrelatedStringMap; + +TEST(RegisteredStringMap, create) { + + RegisteredStringMap regMap; + ASSERT_EQ(0, regMap.getEntries().size()); + ASSERT_FALSE(regMap.isModified()); +} + +TEST(RegisteredStringMap, addNewValues) { + RegisteredStringMap regMap; + + StringID regID0 = regMap.getStringID("RegVal0"); + ASSERT_TRUE(regMap.isModified()); + ASSERT_EQ(1, regMap.getEntries().size()); + + StringID regID1 = regMap.getStringID("RegVal1"); + ASSERT_TRUE(regMap.isModified()); + ASSERT_EQ(2, regMap.getEntries().size()); + + StringID regID2 = regMap.getStringID("RegVal2"); + ASSERT_TRUE(regMap.isModified()); + ASSERT_EQ(3, regMap.getEntries().size()); +} + +class RegisteredStringMapTest : public ::testing::Test { + public: + RegisteredStringMapTest() { + regID0 = regMap.getStringID("RegVal0"); + regID1 = regMap.getStringID("RegVal1"); + regID2 = regMap.getStringID("RegVal2"); + + corID0 = corMap.getStringID("CorVal0"); + corID1 = corMap.getStringID("CorVal1"); + corID2 = corMap.getStringID("CorVal2"); + } + + RegisteredStringMap regMap; + CorrelatedStringMap corMap; + + StringID regID0, regID1, regID2; + StringID corID0, corID1, corID2; +}; + +TEST_F(RegisteredStringMapTest, checkExistingValues) { + + regMap.clearModifiedFlag(); + + StringID regID = regMap.getStringID("RegVal0"); + ASSERT_FALSE(regMap.isModified()); + ASSERT_EQ(3, regMap.getEntries().size()); + ASSERT_EQ(0, regID.value()); + + regID = regMap.getStringID("RegVal1"); + ASSERT_FALSE(regMap.isModified()); + ASSERT_EQ(3, regMap.getEntries().size()); + ASSERT_EQ(1, regID.value()); + + regID = regMap.getStringID("RegVal2"); + ASSERT_FALSE(regMap.isModified()); + ASSERT_EQ(3, regMap.getEntries().size()); + ASSERT_EQ(2, regID.value()); +} + +TEST_F(RegisteredStringMapTest, getValues) { + + ASSERT_EQ("RegVal0", regMap.getStringFromId(regID0)); + ASSERT_EQ("RegVal1", regMap.getStringFromId(regID1)); + ASSERT_EQ("RegVal2", regMap.getStringFromId(regID2)); + ASSERT_TRUE(regMap.getStringFromId(StringID(1000)).empty()); + + ASSERT_STREQ("CorVal0", corMap.getStringFromId(corID0).c_str()); + ASSERT_STREQ("CorVal1", corMap.getStringFromId(corID1).c_str()); + ASSERT_STREQ("CorVal2", corMap.getStringFromId(corID2).c_str()); + ASSERT_EQ(0, std::strlen(corMap.getStringFromId(StringID(1000)).c_str())); +} + +TEST_F(RegisteredStringMapTest, getEntries) { + + auto entries = regMap.getEntries(); + ASSERT_EQ(3, entries.size()); + ASSERT_STREQ("RegVal0", entries[0].c_str()); + ASSERT_STREQ("RegVal1", entries[1].c_str()); + ASSERT_STREQ("RegVal2", entries[2].c_str()); +} + +TEST_F(RegisteredStringMapTest, checkModified) { + regMap.clearModifiedFlag(); + ASSERT_FALSE(regMap.isModified()); +} + +TEST_F(RegisteredStringMapTest, checkOutOfRangePeerID) { + + ASSERT_THROW(corMap.convertPeerToLocalID(PeerStringID(100)), + std::out_of_range); +} + +TEST_F(RegisteredStringMapTest, checkEmptyPeerMapping) { + + StringID emptID = corMap.convertPeerToLocalID(PeerStringID()); + ASSERT_TRUE(emptID.empty()); +} + +TEST_F(RegisteredStringMapTest, checkSetupPeerMappings) { + + auto entries = regMap.getEntries(); + corMap.setupPeerMappings(entries); + + StringID corID3 = corMap.convertPeerToLocalID(PeerStringID(regID0.value())); + StringID corID4 = corMap.convertPeerToLocalID(PeerStringID(regID1.value())); + StringID corID5 = corMap.convertPeerToLocalID(PeerStringID(regID2.value())); + + ASSERT_STREQ("RegVal0", corMap.getStringFromId(corID3).c_str()); + ASSERT_STREQ("RegVal1", corMap.getStringFromId(corID4).c_str()); + ASSERT_STREQ("RegVal2", corMap.getStringFromId(corID5).c_str()); +} diff --git a/tests/cplusplus/Common/Serialization.cpp b/tests/cplusplus/Common/Serialization.cpp index 5bdbc4611..074b2cb22 100644 --- a/tests/cplusplus/Common/Serialization.cpp +++ b/tests/cplusplus/Common/Serialization.cpp @@ -151,6 +151,27 @@ TYPED_TEST(ArithmeticRawSerialization, RoundTrip0) { ASSERT_EQ(reader.bytesRemaining(), 0); } +TYPED_TEST(ArithmeticRawSerialization, VectorRoundTrip) { + Buffer<> buf; + TypeParam in(0); + static const auto count = 5; + std::vector inVal; + for (int i = 0; i < count; ++i) { + in += 1.8; + inVal.push_back(in); + } + + osvr::common::serialization::serializeRaw(buf, inVal); + ASSERT_GE(buf.size(), sizeof(uint32_t) + count * sizeof(TypeParam)); + + auto reader = buf.startReading(); + + std::vector outVal(1); + osvr::common::serialization::deserializeRaw(reader, outVal); + ASSERT_EQ(inVal, outVal); + ASSERT_EQ(reader.bytesRemaining(), 0); +} + #ifdef _MSC_VER /// Disable "truncation of constant value" warning here. #pragma warning(push) @@ -231,6 +252,28 @@ TEST(BoolSerialization, RoundTripFalse) { ASSERT_EQ(reader.bytesRemaining(), 0); } +TEST(StringVectorSerialization, RoundTrip) { + auto buf = Buffer<>{}; + using TypeParam = std::vector; + auto inVal = TypeParam{}; + auto os = std::ostringstream{}; + static const auto count = 10; + for (int i = 0; i < count; ++i) { + inVal.push_back(os.str()); + os << "x"; + } + + osvr::common::serialization::serializeRaw(buf, inVal); + ASSERT_GE(buf.size(), sizeof(uint32_t) + count * sizeof(char)); + + auto reader = buf.startReading(); + + auto outVal = TypeParam{}; + osvr::common::serialization::deserializeRaw(reader, outVal); + ASSERT_EQ(inVal, outVal); + ASSERT_EQ(reader.bytesRemaining(), 0); +} + class SerializationAlignment : public ::testing::Test { public: virtual void SetUp() {