#include #include #include #include #include using namespace mbgl; namespace { constexpr uint64_t Hour = 3'600'000; constexpr uint64_t Base = 1'700'000'000'000ULL; particle::DecodedFieldPlane makePlane(uint16_t semantic, uint16_t group, particle::FieldValueClass valueClass, particle::FieldComponent component, uint16_t unit, std::string identity, std::shared_ptr> bytes) { return {{semantic, group, component}, particle::FieldDType::UInt8, valueClass, unit, false, 4, 1.0f, 0.0f, std::move(identity), std::move(bytes)}; } std::shared_ptr makeFrame(std::string field, uint64_t time, uint64_t generation, std::string suffix, bool vector = true, bool scalar = true) { const auto bytes = std::make_shared>(std::vector{1, 2, 3, 4}); const auto bits = std::make_shared>(std::vector{15}); particle::DecodedFieldFrame frame; frame.frameContentIdentity = {std::move(field), generation, generation, time}; frame.referenceTimeMs = time; frame.grid = {.layout = 1, .projection = 1, .sampleOrder = 1, .gridRegistration = 1, .width = 2, .height = 2, .west = 0, .south = 0, .east = 1, .north = 1}; frame.validityContentIdentity = "valid-" + suffix; frame.validityBits = bits; frame.validityPlane = makePlane(900, 0, particle::FieldValueClass::Validity, particle::FieldComponent::None, particle::FieldUnitNone, frame.validityContentIdentity, bits); frame.validityPlane.dtype = particle::FieldDType::Bitset; frame.validityPlane.requiredFlag = true; if (vector) { frame.planes.push_back(makePlane(1, 7, particle::FieldValueClass::VelocityComponent, particle::FieldComponent::UEast, particle::FieldUnitMetersPerSecond, "u-" + suffix, bytes)); frame.planes.push_back(makePlane(2, 7, particle::FieldValueClass::VelocityComponent, particle::FieldComponent::VNorth, particle::FieldUnitMetersPerSecond, "v-" + suffix, bytes)); } if (scalar) frame.planes.push_back(makePlane(50, 0, particle::FieldValueClass::Scalar, particle::FieldComponent::None, 42, "s-" + suffix, bytes)); return std::make_shared(std::move(frame)); } void submit(particle::EncodedFieldStore& store, const std::string& key, std::shared_ptr frame) { ASSERT_EQ(store.submit(key, std::move(frame)), particle::FieldSeriesSubmitResult::Inserted); } particle::ColorBinding color(std::string key = "weather.wave") { return {std::move(key), {50, 0, particle::FieldComponent::None}, particle::FieldValueClass::Scalar, 42}; } particle::ParticleBinding wind() { particle::ParticleBinding binding; binding.fieldKey = "weather.wind"; binding.uPlane = {1, 7, particle::FieldComponent::None}; binding.vPlane = {2, 7, particle::FieldComponent::None}; binding.expectedUnitID = particle::FieldUnitMetersPerSecond; return binding; } } // namespace TEST(ParticleFieldBinding, ResolvesIndependentFieldKeyCadence) { particle::EncodedFieldStore store; for (uint64_t hour : {0ULL, 1ULL, 2ULL}) submit(store, "weather.wind", makeFrame("wind", Base + hour * Hour, 1, "w" + std::to_string(hour))); for (uint64_t hour : {0ULL, 3ULL, 6ULL}) submit(store, "weather.wave", makeFrame("wave", Base + hour * Hour, 1, "a" + std::to_string(hour))); const auto c = particle::BindingProjectionResolver::resolve(store, color(), Base + Hour + Hour / 2); const auto p = particle::BindingProjectionResolver::resolve(store, wind(), Base + Hour + Hour / 2); ASSERT_TRUE(c.accepted()); ASSERT_TRUE(p.accepted()); EXPECT_FLOAT_EQ(c.projection->sampleState.mix, 0.5f); EXPECT_FLOAT_EQ(p.projection->sampleState.mix, 0.5f); EXPECT_EQ(c.projection->contentIdentity.t0Frame.validTimeMs, Base); EXPECT_EQ(c.projection->contentIdentity.t1Frame.validTimeMs, Base + 3 * Hour); EXPECT_EQ(p.projection->contentIdentity.t0Frame.validTimeMs, Base + Hour); EXPECT_EQ(p.projection->contentIdentity.t1Frame.validTimeMs, Base + 2 * Hour); EXPECT_NE(c.projection->t0Frame, p.projection->t0Frame); EXPECT_NE(c.projection->t0ValidityStorage, p.projection->t0ValidityStorage); } TEST(ParticleFieldBinding, MixChangesSampleStateButNotContentIdentityOrStorage) { particle::EncodedFieldStore store; submit(store, "weather.wave", makeFrame("wave", Base, 1, "0")); submit(store, "weather.wave", makeFrame("wave", Base + 4 * Hour, 1, "4")); const auto at0 = particle::BindingProjectionResolver::resolve(store, color(), Base); const auto at1 = particle::BindingProjectionResolver::resolve(store, color(), Base + Hour); const auto at2 = particle::BindingProjectionResolver::resolve(store, color(), Base + 2 * Hour); const auto at3 = particle::BindingProjectionResolver::resolve(store, color(), Base + 14'256'000); ASSERT_TRUE(at0.accepted()); ASSERT_TRUE(at1.accepted()); ASSERT_TRUE(at2.accepted()); ASSERT_TRUE(at3.accepted()); EXPECT_FLOAT_EQ(at0.projection->sampleState.mix, 0.0f); EXPECT_FLOAT_EQ(at1.projection->sampleState.mix, 0.25f); EXPECT_FLOAT_EQ(at2.projection->sampleState.mix, 0.5f); EXPECT_FLOAT_EQ(at3.projection->sampleState.mix, 0.99f); EXPECT_EQ(at0.projection->contentIdentity, at1.projection->contentIdentity); EXPECT_EQ(at0.projection->contentIdentity, at2.projection->contentIdentity); EXPECT_EQ(at0.projection->contentIdentity, at3.projection->contentIdentity); EXPECT_EQ(at0.projection->t0PlaneStorage, at3.projection->t0PlaneStorage); } TEST(ParticleFieldBinding, ScalarRevisionChangesOnlyColorIdentity) { particle::EncodedFieldStore store; submit(store, "weather.wind", makeFrame("wind", Base, 1, "0")); submit(store, "weather.wind", makeFrame("wind", Base + Hour, 1, "1")); const auto beforeColor = particle::BindingProjectionResolver::resolve(store, color("weather.wind"), Base + Hour / 2); const auto beforeParticle = particle::BindingProjectionResolver::resolve(store, wind(), Base + Hour / 2); ASSERT_TRUE(beforeColor.accepted()); ASSERT_TRUE(beforeParticle.accepted()); auto revision = *beforeParticle.projection->t0Frame; revision.frameContentIdentity.generation = 2; revision.frameContentIdentity.dataVersion = 2; revision.planes.back().contentIdentity = "s-revised"; ASSERT_EQ(store.submit("weather.wind", std::make_shared(std::move(revision))), particle::FieldSeriesSubmitResult::ReplacedRevision); const auto afterColor = particle::BindingProjectionResolver::resolve(store, color("weather.wind"), Base + Hour / 2); const auto afterParticle = particle::BindingProjectionResolver::resolve(store, wind(), Base + Hour / 2); ASSERT_TRUE(afterColor.accepted()); ASSERT_TRUE(afterParticle.accepted()); EXPECT_NE(beforeColor.projection->contentIdentity, afterColor.projection->contentIdentity); EXPECT_EQ(beforeParticle.projection->contentIdentity, afterParticle.projection->contentIdentity); EXPECT_EQ(beforeParticle.projection->t0FirstPlaneStorage, afterParticle.projection->t0FirstPlaneStorage); EXPECT_EQ(beforeParticle.projection->t0ValidityStorage, afterParticle.projection->t0ValidityStorage); } TEST(ParticleFieldBinding, FailsClosedAndSupportsDirectionAngle) { particle::EncodedFieldStore store; submit(store, "weather.wind", makeFrame("wind", Base, 1, "0")); submit(store, "weather.wind", makeFrame("wind", Base + Hour, 1, "1")); EXPECT_EQ(particle::BindingProjectionResolver::resolve(store, color(""), Base).error, particle::BindingProjectionError::InvalidBinding); EXPECT_EQ(particle::BindingProjectionResolver::resolve(store, color("missing"), Base).error, particle::BindingProjectionError::FieldSeriesMissing); EXPECT_EQ(particle::BindingProjectionResolver::resolve(store, color("weather.wind"), Base + 2 * Hour).error, particle::BindingProjectionError::WindowUnavailable); auto missing = color("weather.wind"); missing.plane.semanticID = 99; EXPECT_EQ(particle::BindingProjectionResolver::resolve(store, missing, Base).error, particle::BindingProjectionError::PlaneMissing); auto classMismatch = color("weather.wind"); classMismatch.plane = {1, 7, particle::FieldComponent::None}; EXPECT_EQ(particle::BindingProjectionResolver::resolve(store, classMismatch, Base).error, particle::BindingProjectionError::PlaneClassMismatch); auto unitMismatch = wind(); unitMismatch.expectedUnitID = 99; EXPECT_EQ(particle::BindingProjectionResolver::resolve(store, unitMismatch, Base).error, particle::BindingProjectionError::PlaneUnitMismatch); auto componentMismatch = wind(); componentMismatch.uPlane.semanticID = 2; componentMismatch.vPlane.semanticID = 1; EXPECT_EQ(particle::BindingProjectionResolver::resolve(store, componentMismatch, Base).error, particle::BindingProjectionError::PlaneComponentMismatch); particle::EncodedFieldStore unequalGroups; auto g0 = *makeFrame("wind", Base, 1, "g0"), g1 = *makeFrame("wind", Base + Hour, 1, "g1"); g0.planes[1].key.groupID = 8; g1.planes[1].key.groupID = 8; submit(unequalGroups, "weather.wind", std::make_shared(std::move(g0))); submit(unequalGroups, "weather.wind", std::make_shared(std::move(g1))); auto groupMismatch = wind(); groupMismatch.vPlane.groupID = 8; EXPECT_EQ(particle::BindingProjectionResolver::resolve(unequalGroups, groupMismatch, Base).error, particle::BindingProjectionError::PlaneGroupMismatch); particle::EncodedFieldStore directions; auto d0 = *makeFrame("direction", Base, 1, "d0", false, false); auto d1 = *makeFrame("direction", Base + Hour, 1, "d1", false, false); const auto directionBytes = std::make_shared>(std::vector{1, 2, 3, 4}); for (auto* frame : {&d0, &d1}) { frame->planes.push_back(makePlane(10, 0, particle::FieldValueClass::DirectionAngle, particle::FieldComponent::None, particle::FieldUnitDegrees, "direction", directionBytes)); frame->planes.back().dtype = particle::FieldDType::UInt16; frame->planes.back().scale = 360.0f / 65536.0f; } submit(directions, "weather.direction", std::make_shared(std::move(d0))); submit(directions, "weather.direction", std::make_shared(std::move(d1))); particle::ParticleBinding direction; direction.mode = particle::ParticleBindingMode::DirectionAngle; direction.fieldKey = "weather.direction"; direction.directionPlane = {10, 0, particle::FieldComponent::None}; direction.expectedUnitID = particle::FieldUnitDegrees; EXPECT_TRUE(particle::BindingProjectionResolver::resolve(directions, direction, Base).accepted()); direction.expectedUnitID = particle::FieldUnitMetersPerSecond; EXPECT_EQ(particle::BindingProjectionResolver::resolve(directions, direction, Base).error, particle::BindingProjectionError::PlaneUnitMismatch); direction.expectedUnitID = particle::FieldUnitDegrees; direction.directionPlane.semanticID = 99; EXPECT_EQ(particle::BindingProjectionResolver::resolve(directions, direction, Base).error, particle::BindingProjectionError::PlaneMissing); }