Files
SEQUA-Engine/3d/wire/tests/test_wire_contract.cpp

155 lines
6.2 KiB
C++

// SPDX-License-Identifier: MPL-2.0
// scene/delta en FlatBuffers (contrat contract/, ADR 0013, lot B) : aller-retour exact,
// valeurs refusées par les contrôles générés.
#include "sequa/3d/wire_contract.hpp"
#include <doctest/doctest.h>
#include <cstdint>
#include <limits>
#include <span>
#include <string>
#include <vector>
using namespace sequa::three_d;
using namespace sequa::three_d::wire;
namespace cr = sequa::core_render;
namespace common = sequa::common;
namespace {
std::span<const std::uint8_t> bytes_of(const std::string& frame)
{
return {reinterpret_cast<const std::uint8_t*>(frame.data()), frame.size()};
}
std::string frame_bytes(const flatbuffers::FlatBufferBuilder& builder)
{
return {reinterpret_cast<const char*>(builder.GetBufferPointer()), builder.GetSize()};
}
SceneDeltaMessage every_command()
{
Transform transform;
transform.position = {1.5f, -2.25f, 1e-7f};
transform.rotation = {0, 0.70710677f, 0, 0.70710677f};
transform.scale = {2, 1, 0.5f};
Visual visual;
visual.primitive = Primitive::capsule;
visual.size = {0.5f, 1.8f, 0.5f};
visual.color = {0.1f, 0.2f, 0.3f, 0.4f};
visual.semantic_class = 0xFFFFFFFFU;
Camera camera;
camera.transform = transform;
camera.vertical_fov_deg = 55;
camera.near_plane = 0.1f;
camera.far_plane = 500;
SceneDeltaMessage message;
message.delta.world_version = (1ULL << 53) + 7; // au-delà de ce qu'un double JS garde exactement
message.tick = 0xFFFFFFFFFFFFFFFFULL;
message.full = true;
message.sent_at_us = 1'000'000'123ULL;
message.delta.commands = {CreateObject{0x10, transform, visual}, SetTransform{0x10, transform},
SetVisual{0x10, visual}, SetCamera{camera},
DestroyObject{0xFFFFFFFFFFFFFFFFULL}};
return message;
}
// Trame scene/delta d'une seule commande SetTransform, écrite à la main.
std::string set_transform_frame(std::uint64_t id, float x)
{
flatbuffers::FlatBufferBuilder builder;
const common::Transform transform(common::Vec3(x, 0, 0), common::Quat(0, 0, 0, 1), common::Vec3(1, 1, 1));
const std::vector<cr::SceneCommand> types{cr::SceneCommand::SetTransform};
const std::vector<flatbuffers::Offset<void>> tables{cr::CreateSetTransform(builder, id, &transform).Union()};
const auto types_vector = builder.CreateVector(types);
const auto tables_vector = builder.CreateVector(tables);
cr::contract::finish_scene_delta(builder, 0,
cr::CreateSceneDelta(builder, 1, 1, false, 1, types_vector, tables_vector));
return frame_bytes(builder);
}
} // namespace
TEST_CASE("scene/delta FlatBuffers : aller-retour exact de toutes les commandes")
{
const SceneDeltaMessage message = every_command();
const std::string frame = encode_scene_delta_frame(message);
std::string error;
const auto back = decode_scene_delta_frame(bytes_of(frame), &error);
REQUIRE_MESSAGE(back.has_value(), error);
CHECK(back->delta == message.delta);
CHECK(back->tick == message.tick);
CHECK(back->full == message.full);
CHECK(back->sent_at_us == message.sent_at_us);
// Identifiant de la liaison core-render dans les octets 4 à 7.
REQUIRE(frame.size() > 8);
CHECK(frame.substr(4, 4) == "SQCR");
}
TEST_CASE("scene/delta FlatBuffers : delta vide, full absent vaut faux")
{
SceneDeltaMessage message;
message.delta.world_version = 3;
message.tick = 2;
message.sent_at_us = 1;
const auto back = decode_scene_delta_frame(bytes_of(encode_scene_delta_frame(message)));
REQUIRE(back.has_value());
CHECK(back->delta.commands.empty());
CHECK_FALSE(back->full);
CHECK(back->delta.world_version == 3);
}
TEST_CASE("scene/delta FlatBuffers : valeurs refusees par les controles generes")
{
std::string error;
CHECK(decode_scene_delta_frame(bytes_of(set_transform_frame(5, 1.0f))).has_value());
SUBCASE("id 0 réservé")
{
CHECK_FALSE(decode_scene_delta_frame(bytes_of(set_transform_frame(0, 1.0f)), &error).has_value());
CHECK(error.find("id") != std::string::npos);
}
SUBCASE("nombre non fini")
{
CHECK_FALSE(decode_scene_delta_frame(bytes_of(set_transform_frame(5, std::numeric_limits<float>::infinity())), &error)
.has_value());
CHECK_FALSE(decode_scene_delta_frame(bytes_of(set_transform_frame(5, std::numeric_limits<float>::quiet_NaN())))
.has_value());
}
SUBCASE("primitive inconnue")
{
flatbuffers::FlatBufferBuilder builder;
const common::Vec3 size(1, 1, 1);
const common::Color color(1, 1, 1, 1);
const auto visual = cr::CreateVisual(builder, static_cast<cr::Primitive>(9), &size, &color, 0U);
const std::vector<cr::SceneCommand> types{cr::SceneCommand::SetVisual};
const std::vector<flatbuffers::Offset<void>> tables{cr::CreateSetVisual(builder, 4, visual).Union()};
const auto types_vector = builder.CreateVector(types);
const auto tables_vector = builder.CreateVector(tables);
cr::contract::finish_scene_delta(builder, 0,
cr::CreateSceneDelta(builder, 1, 1, false, 1, types_vector, tables_vector));
CHECK_FALSE(decode_scene_delta_frame(bytes_of(frame_bytes(builder)), &error).has_value());
CHECK(error.find("primitive") != std::string::npos);
}
SUBCASE("champ obligatoire absent (tick)")
{
flatbuffers::FlatBufferBuilder builder;
const auto types_vector = builder.CreateVector(std::vector<cr::SceneCommand>{});
const auto tables_vector = builder.CreateVector(std::vector<flatbuffers::Offset<void>>{});
cr::contract::finish_scene_delta(
builder, 0, cr::CreateSceneDelta(builder, 1, flatbuffers::nullopt, false, 1, types_vector, tables_vector));
CHECK_FALSE(decode_scene_delta_frame(bytes_of(frame_bytes(builder)), &error).has_value());
CHECK(error.find("tick") != std::string::npos);
}
SUBCASE("trame tronquée")
{
const std::string frame = encode_scene_delta_frame(every_command());
for (std::size_t size = 0; size < frame.size(); size += 7) {
CHECK_FALSE(decode_scene_delta_frame(bytes_of(frame.substr(0, size))).has_value());
}
}
}