diff options
-rw-r--r-- | compat/int-hash.cpp | 78 | ||||
-rw-r--r-- | compat/int-hash.hpp | 14 | ||||
-rw-r--r-- | editor/app.cpp | 2 | ||||
-rw-r--r-- | serialize/wall-atlas.cpp | 100 | ||||
-rw-r--r-- | src/point.cpp | 4 | ||||
-rw-r--r-- | src/wall-atlas.cpp | 42 | ||||
-rw-r--r-- | src/wall-atlas.hpp | 9 | ||||
-rw-r--r-- | src/world.cpp | 4 | ||||
-rw-r--r-- | test/hash.cpp | 34 | ||||
-rw-r--r-- | test/json/frameset-header2.json | 9 | ||||
-rw-r--r-- | test/wall-atlas.cpp | 12 |
11 files changed, 221 insertions, 87 deletions
diff --git a/compat/int-hash.cpp b/compat/int-hash.cpp index 5847e284..aa5287f8 100644 --- a/compat/int-hash.cpp +++ b/compat/int-hash.cpp @@ -6,72 +6,88 @@ namespace floormat { namespace { +template<typename T> struct fnvhash_params; + +template<> struct fnvhash_params<uint32_t> { uint32_t a = 0x811c9dc5u, b = 0x01000193u; }; +template<> struct fnvhash_params<uint64_t> { uint64_t a = 0xcbf29ce484222325u, b = 0x100000001b3u; }; + [[maybe_unused]] -CORRADE_ALWAYS_INLINE uint32_t FNVHash_32(uint32_t x) +CORRADE_ALWAYS_INLINE size_t fnvhash_uint_32(uint32_t x) { - const auto *str = (const char*)&x, *end = str + 4; - uint32_t hash = 0x811c9dc5u; -fm_UNROLL_4 - for (; str != end; ++str) - { - hash *= 0x01000193u; - hash ^= (uint8_t)*str; - } + constexpr auto params = fnvhash_params<std::common_type_t<size_t>>{}; + constexpr auto a = params.a, b = params.b; + auto hash = a; + const auto* str = (const char*)&x; + + hash *= b; hash ^= (uint8_t)*str++; // 0 + hash *= b; hash ^= (uint8_t)*str++; // 1 + hash *= b; hash ^= (uint8_t)*str++; // 2 + hash *= b; hash ^= (uint8_t)*str++; // 3 + return hash; } -CORRADE_ALWAYS_INLINE uint64_t FNVHash_64(uint64_t x) +CORRADE_ALWAYS_INLINE size_t fnvhash_uint_64(uint64_t x) { - const auto *str = (const char*)&x, *end = str + 8; - uint64_t hash = 0xcbf29ce484222325u; -fm_UNROLL_8 - for (; str != end; ++str) - { - hash *= 0x100000001b3u; - hash ^= (uint8_t)*str; - } + constexpr auto params = fnvhash_params<std::common_type_t<size_t>>{}; + constexpr auto a = params.a, b = params.b; + auto hash = a; + const auto* str = (const char*)&x; + + hash *= b; hash ^= (uint8_t)*str++; // 0 + hash *= b; hash ^= (uint8_t)*str++; // 1 + hash *= b; hash ^= (uint8_t)*str++; // 2 + hash *= b; hash ^= (uint8_t)*str++; // 3 + hash *= b; hash ^= (uint8_t)*str++; // 4 + hash *= b; hash ^= (uint8_t)*str++; // 5 + hash *= b; hash ^= (uint8_t)*str++; // 6 + hash *= b; hash ^= (uint8_t)*str++; // 7 + return hash; } } // namespace -uint64_t fnvhash_32(const void* buf, size_t size) +uint32_t hash_32(const void* buf, size_t size) noexcept { + constexpr auto params = fnvhash_params<std::common_type_t<uint32_t>>{}; + constexpr auto a = params.a, b = params.b; + auto hash = a; const auto *str = (const char*)buf, *const end = str + size; - uint32_t hash = 0x811c9dc5u; + fm_UNROLL_4 for (; str != end; ++str) { - hash *= 0x01000193u; + hash *= b; hash ^= (uint8_t)*str; } return hash; } -uint64_t fnvhash_64(const void* buf, size_t size) +uint64_t hash_64(const void* buf, size_t size) noexcept { + constexpr auto params = fnvhash_params<std::common_type_t<size_t>>{}; + constexpr auto a = params.a, b = params.b; + auto hash = a; const auto *str = (const char*)buf, *const end = str + size; - uint64_t hash = 0xcbf29ce484222325u; + fm_UNROLL_8 for (; str != end; ++str) { - hash *= 0x100000001b3u; + hash *= b; hash ^= (uint8_t)*str; } return hash; } -size_t int_hash(uint32_t x) noexcept +size_t hash_int(uint32_t x) noexcept { - if constexpr(sizeof(size_t) == 4) - return FNVHash_32(x); - else - return FNVHash_64(x); + return fnvhash_uint_32(x); } -size_t int_hash(uint64_t x) noexcept +size_t hash_int(uint64_t x) noexcept { - return FNVHash_64(x); + return fnvhash_uint_64(x); } } // namespace floormat diff --git a/compat/int-hash.hpp b/compat/int-hash.hpp index 86fc6a45..b3dd7616 100644 --- a/compat/int-hash.hpp +++ b/compat/int-hash.hpp @@ -1,11 +1,17 @@ #pragma once +namespace floormat::impl_hash { + +template<size_t N> size_t hash_buf(const void* buf, size_t size) noexcept = delete; + +} // namespace floormat::impl_hash + namespace floormat { -size_t int_hash(uint32_t x) noexcept; -size_t int_hash(uint64_t x) noexcept; +uint64_t hash_64(const void* buf, size_t size) noexcept; +uint32_t hash_32(const void* buf, size_t size) noexcept; -uint64_t fnvhash_64(const void* buf, size_t size); -uint64_t fnvhash_32(const void* buf, size_t size); +size_t hash_int(uint32_t x) noexcept; +size_t hash_int(uint64_t x) noexcept; } // namespace floormat diff --git a/editor/app.cpp b/editor/app.cpp index 8f3694ab..7505ff84 100644 --- a/editor/app.cpp +++ b/editor/app.cpp @@ -17,7 +17,7 @@ namespace floormat { -struct Optional<struct point> cursor_state::point() const +Optional<struct point> cursor_state::point() const { if (tile) return {InPlaceInit, *tile, *subpixel}; diff --git a/serialize/wall-atlas.cpp b/serialize/wall-atlas.cpp index 4a709060..31090e45 100644 --- a/serialize/wall-atlas.cpp +++ b/serialize/wall-atlas.cpp @@ -13,6 +13,8 @@ namespace floormat { namespace { +using nlohmann::json; +constexpr auto none = (uint8_t)-1; using namespace std::string_literals; constexpr StringView rotation_names[] = { "n"_s, "e"_s, "s"_s, "w"_s, }; @@ -33,54 +35,33 @@ StringView rotation_to_name(size_t i) return rotation_names[i]; } -void read_frameset_metadata(const nlohmann::json& j, wall_frames& val, size_t& rot) +[[nodiscard]] wall_frames read_frames_metadata(const json& jfs) { - val = {}; - rot = rotation_from_name(std::string{j["name"s]}); + wall_frames val; - if (j.contains("pixel-size"s)) - val.pixel_size = j["pixel-size"s]; - if (j.contains("tint"s)) + if (jfs.contains("pixel-size"s)) + val.pixel_size = jfs["pixel-size"s]; + if (jfs.contains("tint"s)) { - std::tie(val.tint_mult, val.tint_add) = std::pair<Vector4, Vector3>{j["tint"s]}; + std::tie(val.tint_mult, val.tint_add) = std::pair<Vector4, Vector3>{ jfs["tint"s]}; fm_soft_assert(val.tint_mult >= Color4{0}); } - if (j.contains("from-rotation"s)) - val.from_rotation = (uint8_t)rotation_from_name(std::string{j["from-rotation"s]}); - if (j.contains("mirrored"s)) - val.mirrored = !!j["mirrored"s]; - if (j.contains("use-default-tint"s)) - val.use_default_tint = !!j["use-default-tint"s]; + if (jfs.contains("from-rotation"s)) + val.from_rotation = (uint8_t)rotation_from_name(std::string{jfs["from-rotation"s]}); + if (jfs.contains("mirrored"s)) + val.mirrored = jfs["mirrored"s]; + if (jfs.contains("use-default-tint"s)) + val.use_default_tint = jfs["use-default-tint"s]; + + return val; } -void write_frameset_metadata(nlohmann::json& j, const wall_atlas& a, const wall_frames& val, size_t rot) +[[nodiscard]] wall_frame_set read_frameset_metadata(const json& j) { - constexpr wall_frames default_value; - - fm_soft_assert(val.count != (uint32_t)-1); - fm_soft_assert(val.index == (uint32_t)-1 || val.index < a.frame_array().size()); - fm_soft_assert((val.index == (uint32_t)-1) == (val.count == 0)); - - j["name"s] = rotation_to_name(rot); - j["pixel-size"s] = val.pixel_size; - if (val.tint_mult != default_value.tint_mult || val.tint_add != default_value.tint_add) - { - auto tint = std::pair<Vector4, Vector3>{{val.tint_mult}, {val.tint_add}}; - j["tint"s] = tint; - } - if (val.from_rotation != default_value.from_rotation) - { - fm_soft_assert(val.from_rotation != (uint8_t)-1 && val.from_rotation < 4); - j["from-rotation"s] = val.from_rotation; - } - if (val.mirrored != default_value.mirrored) - j["mirrored"s] = val.mirrored; - if (val.use_default_tint) - if (val.tint_mult != default_value.tint_mult || val.tint_add != default_value.tint_add) - j["use-default-tint"s] = true; + return {}; } -void read_framesets(const nlohmann::json& jf, wall_atlas_def& val) +void read_framesets(const json& jf, wall_atlas_def& val) { fm_soft_assert(jf.is_object()); fm_soft_assert(val.framesets == nullptr && val.frameset_count == 0); @@ -95,7 +76,7 @@ void read_framesets(const nlohmann::json& jf, wall_atlas_def& val) { fm_soft_assert(jf[key].is_object()); auto& index = val.frameset_indexes[i]; - fm_soft_assert(index == (uint8_t)-1); + fm_soft_assert(index == none); index = count++; } } @@ -104,11 +85,50 @@ void read_framesets(const nlohmann::json& jf, wall_atlas_def& val) val.framesets = std::make_unique<wall_frame_set[]>(count); val.frameset_count = count; + + for (auto i = 0uz; i < 4; i++) + { + auto index = val.frameset_indexes[i]; + if (index == none) + continue; + auto r = rotation_to_name(i); + auto key = std::string_view{r.data(), r.size()}; + + fm_debug_assert(index < val.frameset_count); + val.framesets[index] = read_frameset_metadata(jf[key]); + } +} + +void write_frameset_metadata(json& j, const wall_atlas& a, const wall_frames& val, size_t rot) +{ + constexpr wall_frames default_value; + + fm_soft_assert(val.count != (uint32_t)-1); + fm_soft_assert(val.index == (uint32_t)-1 || val.index < a.frame_array().size()); + fm_soft_assert((val.index == (uint32_t)-1) == (val.count == 0)); + + j["name"s] = rotation_to_name(rot); + j["pixel-size"s] = val.pixel_size; + if (val.tint_mult != default_value.tint_mult || val.tint_add != default_value.tint_add) + { + auto tint = std::pair<Vector4, Vector3>{{val.tint_mult}, {val.tint_add}}; + j["tint"s] = tint; + } + if (val.from_rotation != default_value.from_rotation) + { + fm_soft_assert(val.from_rotation != none && val.from_rotation < 4); + j["from-rotation"s] = val.from_rotation; + } + if (val.mirrored != default_value.mirrored) + j["mirrored"s] = val.mirrored; + if (val.use_default_tint) + if (val.tint_mult != default_value.tint_mult || val.tint_add != default_value.tint_add) + j["use-default-tint"s] = true; } } // namespace -void Serialize::wall_test::read_atlas_header(const nlohmann::json& j, wall_atlas_def& val) +void Serialize::wall_test::read_atlas_header(const json& j, wall_atlas_def& val) { val = {}; val.info = { std::string(j["name"s]), j["depth"], }; diff --git a/src/point.cpp b/src/point.cpp index a6e27f66..3e970ba3 100644 --- a/src/point.cpp +++ b/src/point.cpp @@ -9,10 +9,10 @@ size_t point::hash() const static_assert(sizeof *this == size); #ifdef FLOORMAT_64 static_assert(sizeof nullptr > 4); - return fnvhash_64(this, sizeof *this); + return hash_64(this, sizeof *this); #else static_assert(sizeof nullptr == 4); - return fnvhash_32(this, sizeof *this); + return hash_32(this, sizeof *this); #endif } diff --git a/src/wall-atlas.cpp b/src/wall-atlas.cpp index be4010c7..d73510bb 100644 --- a/src/wall-atlas.cpp +++ b/src/wall-atlas.cpp @@ -1,11 +1,32 @@ #include "wall-atlas.hpp" #include "compat/assert.hpp" +#include "compat/function2.hpp" #include <utility> #include <Magnum/ImageView.h> #include <Magnum/GL/TextureFormat.h> namespace floormat { +namespace { + +#define FM_FRAMESET_ITER(Name) do { if (fun( #Name ## _s, const_cast<Self>(frameset.Name), wall_frame_set::type::Name)) return; } while(false) +#define FM_FRAMESET_ITER2(Str, Name) do { if (fun( Str, const_cast<Self>(frameset.Name), wall_frame_set::type::Name )) return; } while(false) + +template<typename Self> +CORRADE_ALWAYS_INLINE void visit_frameset_impl(const wall_frame_set& frameset, auto&& fun) +{ + FM_FRAMESET_ITER(wall); + FM_FRAMESET_ITER(overlay); + FM_FRAMESET_ITER(side); + FM_FRAMESET_ITER(top); + FM_FRAMESET_ITER2("corner-L"_s, corner_L); + FM_FRAMESET_ITER2("corner-R"_s, corner_R); +} + +#undef FM_FRAMESET_ITER + +} // namespace + size_t wall_atlas::enum_to_index(enum rotation r) { static_assert(rotation_COUNT == rotation{8}); @@ -16,6 +37,17 @@ size_t wall_atlas::enum_to_index(enum rotation r) return x; } +bool wall_frames::is_empty() const noexcept +{ + return count == 0; +} + +bool wall_frame_set::is_empty() const noexcept +{ + return !wall.is_empty() && !overlay.is_empty() && !side.is_empty() && !top.is_empty() && + !corner_L.is_empty() && !corner_R.is_empty(); +} + wall_atlas::wall_atlas(wall_info info, const ImageView2D& image, Array<wall_frame> frames, std::unique_ptr<wall_frame_set[]> framesets, @@ -53,6 +85,16 @@ ArrayView<const wall_frame> wall_atlas::frame_array() const { return _frame_arra StringView wall_atlas::name() const { return _info.name; } const wall_frame_set& wall_atlas::frameset(enum rotation r) const { return frameset(enum_to_index(r)); } +void wall_frame_set::visit(const fu2::function_view<bool(StringView name, const wall_frames& frames, type tag) const>& fun) const& +{ + visit_frameset_impl<const wall_frames&>(*this, fun); +} + +void wall_frame_set::visit(const fu2::function_view<bool(StringView name, wall_frames& frames, type tag) const>& fun) & +{ + visit_frameset_impl<wall_frames&>(*this, fun); +} + const wall_frame_set& wall_atlas::frameset(size_t i) const { fm_assert(i < 4 && _frameset_indexes[i] != (uint8_t)-1); diff --git a/src/wall-atlas.hpp b/src/wall-atlas.hpp index aa18c0bc..544bc5b7 100644 --- a/src/wall-atlas.hpp +++ b/src/wall-atlas.hpp @@ -1,5 +1,6 @@ #pragma once #include "compat/defs.hpp" +#include "compat/function2.fwd.hpp" #include "src/rotation.hpp" #include <array> #include <memory> @@ -20,6 +21,7 @@ struct wall_frame struct wall_frames { + bool is_empty() const noexcept; ArrayView<const wall_frame> items(const wall_atlas& a) const; uint32_t index = (uint32_t)-1, count = 0; @@ -34,6 +36,13 @@ struct wall_frames struct wall_frame_set { + enum class type : uint8_t { wall = 1, overlay, side, top, corner_L, corner_R, }; + + bool is_empty() const noexcept; + + void visit(const fu2::function_view<bool(StringView, const wall_frames&, type) const>& fun) const&; + void visit(const fu2::function_view<bool(StringView, wall_frames&, type) const>& fun) &; + wall_frames wall, overlay, side, top; wall_frames corner_L, corner_R; }; diff --git a/src/world.cpp b/src/world.cpp index 5f2bb870..7920c205 100644 --- a/src/world.cpp +++ b/src/world.cpp @@ -6,7 +6,7 @@ using namespace floormat; -size_t world::object_id_hasher::operator()(object_id id) const noexcept { return int_hash(id); } +size_t world::object_id_hasher::operator()(object_id id) const noexcept { return hash_int(id); } size_t world::chunk_coords_hasher::operator()(const chunk_coords_& coord) const noexcept { @@ -14,7 +14,7 @@ size_t world::chunk_coords_hasher::operator()(const chunk_coords_& coord) const x |= uint64_t((uint16_t)coord.x) << 0; x |= uint64_t((uint16_t)coord.y) << 16; x |= uint64_t( (uint8_t)coord.z) << 32; - return int_hash(x); + return hash_int(x); } namespace floormat { diff --git a/test/hash.cpp b/test/hash.cpp index 3a8af600..4e8d9d81 100644 --- a/test/hash.cpp +++ b/test/hash.cpp @@ -1,11 +1,31 @@ #include "app.hpp" #include "compat/int-hash.hpp" +#include <Corrade/Containers/StringView.h> #include <bitset> #include <memory> namespace floormat { -void test_app::test_hash() +namespace { + +void test_simple() +{ + constexpr StringView list[] = { "foo"_s, "bar"_s, "bar\0"_s, "bar2"_s, "baz"_s, }; + constexpr auto size = arraySize(list); + + size_t hashes[size] = {}; + for (auto i = 0uz; i < size; i++) + hashes[i] = hash_64(list[i].data(), list[i].size()); + + for (auto i = 0uz; i < size; i++) + for (auto j = i+1; j < size; j++) + { + if (hashes[i] == hashes[j]) + fm_abort("hash collision between '%s' and '%s': 0x%p", list[i].data(), list[j].data(), (void*)hashes[i]); + } +} + +void test_collisions() { constexpr int max = 8; constexpr size_t size = (2*max+1)*(2*max+1)*4 * 10; @@ -25,7 +45,7 @@ void test_app::test_hash() value |= (uint64_t)(uint16_t)(int16_t)i << 0; value |= (uint64_t)(uint16_t)(int16_t)j << 16; value |= (uint64_t)(uint16_t)(int16_t)k << 32; - auto x = (size_t)int_hash(value); + auto x = (size_t)hash_int(value); //Debug {} << "bitset:" << i << j << k << "=" << x % size << "/" << x; x %= size; #if 1 @@ -33,7 +53,7 @@ void test_app::test_hash() fm_abort("test/bitset: %zu collisions at iter %zu id %zu (%d;%d:%d)", num_collisions, iter, x, i, j, k); #else if ((void)max_collisions, bitset.test(x)) - fm_warn("test/bitset: %zu collisions at iter %zu id %zu (%d;%d:%d)", ++num_collisions, iter, x, i, j, k); + fm_warn("test/bitset: %zu collisions at iter %zu id %zu (%d;%d:%d)", ++num_collisions, iter, x, i, j, k); #endif bitset.set(x); } @@ -41,4 +61,12 @@ void test_app::test_hash() } } +} // namespace + +void test_app::test_hash() +{ + test_simple(); + test_collisions(); +} + } // namespace floormat diff --git a/test/json/frameset-header2.json b/test/json/frameset-header2.json new file mode 100644 index 00000000..8b32cabc --- /dev/null +++ b/test/json/frameset-header2.json @@ -0,0 +1,9 @@ +{ + "name": "foo", + "depth": 42, + "framesets": { + "w": {}, + "n": {} + }, + "frames": [] +} diff --git a/test/wall-atlas.cpp b/test/wall-atlas.cpp index b1cdd8d6..720c118f 100644 --- a/test/wall-atlas.cpp +++ b/test/wall-atlas.cpp @@ -4,18 +4,20 @@ #include "serialize/json-helper.hpp" #include "loader/loader.hpp" #include <algorithm> +#include <Corrade/Containers/PairStl.h> #include <Corrade/Utility/Path.h> namespace floormat { namespace ranges = std::ranges; namespace test = floormat::Serialize::wall_test; +using nlohmann::json; namespace { -void test_atlas_header(StringView path) +Pair<wall_atlas_def, json> test_atlas_header(StringView path, StringView filename) { - auto j = json_helper::from_json_(Path::join(path, "frameset-header.json"_s)); + auto j = json_helper::from_json_(Path::join(path, filename)); wall_atlas_def def; test::read_atlas_header(j, def); @@ -34,8 +36,10 @@ void test_atlas_header(StringView path) fm_assert(def.frameset_indexes[W] == 1); else if (def.frameset_indexes[N] == 1) fm_assert(def.frameset_indexes[W] == 0); + else + fm_assert(false); - std::fputs("", stdout); + return {std::move(def), std::move(j)}; } } // namespace @@ -46,7 +50,7 @@ void test_app::test_wall_atlas() const auto path = Path::join(loader.TEMP_PATH, "test/json"_s); fm_assert(Path::isDirectory(path)); - test_atlas_header(path); + test_atlas_header(path, "frameset-header.json"_s); } |