Lots of progress on octrees
This commit is contained in:
1260
luprex/core/cpp/bytell-hash-map.hpp
Normal file
1260
luprex/core/cpp/bytell-hash-map.hpp
Normal file
File diff suppressed because it is too large
Load Diff
1496
luprex/core/cpp/flat-hash-map.hpp
Normal file
1496
luprex/core/cpp/flat-hash-map.hpp
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -76,21 +76,94 @@
|
|||||||
#include "wrap-vector.hpp"
|
#include "wrap-vector.hpp"
|
||||||
#include "wrap-map.hpp"
|
#include "wrap-map.hpp"
|
||||||
#include "wrap-string.hpp"
|
#include "wrap-string.hpp"
|
||||||
|
#include "wrap-bytell-hash-map.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
|
#include "luastack.hpp"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
class PlaneMap;
|
class PlaneMap;
|
||||||
|
class PlaneTree;
|
||||||
|
|
||||||
class PlaneItem : public eng::nevernew {
|
class PlaneScan : public eng::nevernew {
|
||||||
|
public:
|
||||||
friend class PlaneMap;
|
friend class PlaneMap;
|
||||||
|
friend class PlaneTree;
|
||||||
|
enum Shape { BOX, CYLINDER, SPHEROID };
|
||||||
|
private:
|
||||||
|
// The plane to scan.
|
||||||
|
eng::string plane_;
|
||||||
|
|
||||||
|
// The bounding box of the scan.
|
||||||
|
util::XYZ lo_, hi_, center_;
|
||||||
|
|
||||||
|
// If you scan a cylinder or spheroid, it actually
|
||||||
|
// scans the bounding box first, then clips out
|
||||||
|
// the parts that aren't correct.
|
||||||
|
Shape shape_;
|
||||||
|
|
||||||
|
// When true, the items are sorted by ID.
|
||||||
|
// WARNING: setting this to false can create
|
||||||
|
// nondeterminism. Scans by lua should always be sorted.
|
||||||
|
bool sorted_;
|
||||||
|
|
||||||
|
// The special ID, if nonzero, is either PREPENDED to the
|
||||||
|
// results, or OMITTED from the results, depending on omit_special.
|
||||||
|
int64_t special_;
|
||||||
|
bool omit_special_;
|
||||||
|
|
||||||
|
// If this is true, items on the nowhere plane are not scanned.
|
||||||
|
bool omit_nowhere_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PlaneMap *pmap_;
|
// Derived variables. These are populated by PlaneTree::scan.
|
||||||
|
uint64_t bblo_;
|
||||||
|
uint64_t bbhi_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PlaneScan() : plane_(""), shape_(BOX), sorted_(true), special_(0), omit_special_(0), omit_nowhere_(false) {}
|
||||||
|
|
||||||
|
// Convert a lua table into a scan configuration.
|
||||||
|
void configure(LuaStack &LS, LuaSlot slot);
|
||||||
|
|
||||||
|
// Set the bounding box given two corners.
|
||||||
|
void set_bbox_given_two_corners(const util::XYZ &a, const util::XYZ &b) {
|
||||||
|
lo_.x = std::min(a.x, b.x);
|
||||||
|
lo_.y = std::min(a.y, b.y);
|
||||||
|
lo_.z = std::min(a.z, b.z);
|
||||||
|
hi_.x = std::max(a.x, b.x);
|
||||||
|
hi_.y = std::max(a.y, b.y);
|
||||||
|
hi_.z = std::max(a.z, b.z);
|
||||||
|
center_ = (lo_ + hi_) * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the bounding box given a center and a radius.
|
||||||
|
void set_bbox_given_center_radius(const util::XYZ ¢er, float r) {
|
||||||
|
util::XYZ offset(r, r, r);
|
||||||
|
lo_ = center - offset;
|
||||||
|
hi_ = center + offset;
|
||||||
|
center_ = center;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_plane(std::string_view p) { plane_ = p; }
|
||||||
|
void set_shape(Shape s) { shape_ = s; }
|
||||||
|
void set_sorted(bool s) { sorted_ = s; }
|
||||||
|
void set_special(int64_t id, bool omit) { special_ = id; omit_special_ = omit; }
|
||||||
|
void set_omit_nowhere(bool b) { omit_nowhere_ = b; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class PlaneItem : public eng::nevernew {
|
||||||
|
friend class PlaneTree;
|
||||||
|
friend class PlaneMap;
|
||||||
|
private:
|
||||||
|
PlaneTree *tree_;
|
||||||
|
PlaneItem *prev_;
|
||||||
|
PlaneItem *next_;
|
||||||
|
int64_t id_;
|
||||||
eng::string plane_;
|
eng::string plane_;
|
||||||
float x_, y_, z_;
|
float x_, y_, z_;
|
||||||
int64_t id_;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PlaneItem();
|
PlaneItem();
|
||||||
@@ -105,43 +178,49 @@ public:
|
|||||||
const float y() const { return y_; }
|
const float y() const { return y_; }
|
||||||
const float z() const { return z_; }
|
const float z() const { return z_; }
|
||||||
|
|
||||||
void untrack();
|
|
||||||
void track(PlaneMap *pmap);
|
void track(PlaneMap *pmap);
|
||||||
|
void untrack() { track(nullptr); }
|
||||||
|
|
||||||
void set_pos(const eng::string &plane, float x, float y, float z);
|
void set_pos(const eng::string &plane, float x, float y, float z);
|
||||||
void set_xyz(float x, float y, float z) { set_pos(plane_, x, y, z); }
|
void set_xyz(float x, float y, float z);
|
||||||
};
|
};
|
||||||
|
|
||||||
class PlaneMap : public eng::nevernew {
|
class PlaneMap : public eng::nevernew {
|
||||||
friend class PlaneItem;
|
friend class PlaneItem;
|
||||||
private:
|
friend class PlaneTree;
|
||||||
using EltVec = eng::vector<PlaneItem *>;
|
|
||||||
using Plane = eng::map<int64_t, EltVec>;
|
|
||||||
eng::map<eng::string, Plane> planes_;
|
|
||||||
void remove(const eng::string &plane, int64_t cell, PlaneItem *client);
|
|
||||||
void insert(const eng::string &plane, int64_t cell, PlaneItem *client);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using IdVector = util::IdVector;
|
using IdVector = util::IdVector;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float default_radius_;
|
||||||
|
eng::map<eng::string, std::unique_ptr<PlaneTree>> planes_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// No special code is needed for construction or destruction.
|
||||||
PlaneMap();
|
PlaneMap();
|
||||||
~PlaneMap();
|
~PlaneMap();
|
||||||
|
|
||||||
// Caution: scan_radius is not deterministically ordered unless sort=true.
|
// The 'insert' and 'remove' operators are inside class
|
||||||
//
|
// PlaneItem. See PlaneItem::track and PlaneItem::untrack.
|
||||||
// exclude_nowhere - if true, and plane="nowhere", nothing is scanned.
|
|
||||||
// special - an ID that is considered special. If zero, there is no special ID.
|
// Scan the PlaneMap for items, return their IDs.
|
||||||
// omit - if true, remove the special ID from the list.
|
IdVector scan(const PlaneScan &s) const;
|
||||||
// if false, prepend the special ID to the head of the list.
|
|
||||||
//
|
// Set the default radius for all planes.
|
||||||
IdVector scan_radius(const eng::string &plane, float x, float y, float radius, bool exclude_nowhere, int64_t special, bool omit) const;
|
// Maybe we'll make it adaptive some day.
|
||||||
IdVector scan_radius_unsorted(const eng::string &plane, float x, float y, float radius, bool exclude_nowhere, int64_t special, bool omit) const;
|
void set_default_radius(float r) { default_radius_ = r; }
|
||||||
|
|
||||||
|
// Return a debug string for the specified plane.
|
||||||
|
// This is for unit testing.
|
||||||
|
eng::string tree_debug_string(const eng::string &plane) const;
|
||||||
|
eng::string outliers_debug_string(const eng::string &plane) const;
|
||||||
|
|
||||||
|
// Untrack all planeitems. This is for unit testing.
|
||||||
|
void untrack_all();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// unit testing stuff.
|
// unit testing stuff.
|
||||||
friend int lfn_unittests_planemap(lua_State *L);
|
friend int lfn_unittests_planemap(lua_State *L);
|
||||||
EltVec get_cell(const eng::string &plane, int64_t cell) const;
|
|
||||||
int total_cells() const;
|
|
||||||
void clear() { planes_.clear(); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -262,6 +262,16 @@ eng::string hash_to_hex(const HashValue &hash);
|
|||||||
// This is a good hash, but not cryptographically good.
|
// This is a good hash, but not cryptographically good.
|
||||||
uint64_t hash_ints(uint64_t n1, uint64_t n2, uint64_t n3, uint64_t n4);
|
uint64_t hash_ints(uint64_t n1, uint64_t n2, uint64_t n3, uint64_t n4);
|
||||||
|
|
||||||
|
// Hash a single 64-bit integer.
|
||||||
|
// This is a good hash, but not cryptographically good.
|
||||||
|
// Published by David Stafford in his article 'Better Bit Mixing'.
|
||||||
|
inline uint64_t hash_int(uint64_t x) {
|
||||||
|
x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
|
||||||
|
x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
|
||||||
|
x = x ^ (x >> 31);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
// Convert a 64-bit hash value into a floating point number between 0 and 1.
|
// Convert a 64-bit hash value into a floating point number between 0 and 1.
|
||||||
double hash_to_double(uint64_t hash);
|
double hash_to_double(uint64_t hash);
|
||||||
|
|
||||||
@@ -319,6 +329,9 @@ struct XYZ {
|
|||||||
XYZ(float ix, float iy, float iz) { x=ix; y=iy; z=iz; }
|
XYZ(float ix, float iy, float iz) { x=ix; y=iy; z=iz; }
|
||||||
bool operator ==(const XYZ &o) const { return x==o.x && y == o.y && z==o.z; }
|
bool operator ==(const XYZ &o) const { return x==o.x && y == o.y && z==o.z; }
|
||||||
bool operator !=(const XYZ &o) const { return x!=o.x || y != o.y || z!=o.z; }
|
bool operator !=(const XYZ &o) const { return x!=o.x || y != o.y || z!=o.z; }
|
||||||
|
XYZ operator -(const XYZ &o) const { return XYZ(x-o.x, y-o.y, z-o.z); }
|
||||||
|
XYZ operator +(const XYZ &o) const { return XYZ(x+o.x, y+o.y, z+o.z); }
|
||||||
|
XYZ operator *(float scale) const { return XYZ(x*scale, y*scale, z*scale); }
|
||||||
eng::string debug_string() const;
|
eng::string debug_string() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -265,11 +265,17 @@ LuaDefine(tangible_near, "tan,radius,omit_nowhere,omit_self",
|
|||||||
LuaStack LS(L, ltan, lradius, lomit_nowhere, lomit_self, list);
|
LuaStack LS(L, ltan, lradius, lomit_nowhere, lomit_self, list);
|
||||||
World *w = World::fetch_global_pointer(L);
|
World *w = World::fetch_global_pointer(L);
|
||||||
Tangible *tan = w->tangible_get(LS, ltan);
|
Tangible *tan = w->tangible_get(LS, ltan);
|
||||||
double radius = LS.cknumber(lradius);
|
|
||||||
bool omit_nowhere = LS.ckboolean(lomit_nowhere);
|
|
||||||
bool omit_self = LS.ckboolean(lomit_self);
|
|
||||||
const AnimStep &aqback = tan->anim_queue_.back();
|
const AnimStep &aqback = tan->anim_queue_.back();
|
||||||
util::IdVector idv = w->plane_map_.scan_radius(aqback.plane(), aqback.xyz().x, aqback.xyz().y, radius, omit_nowhere, tan->id(), omit_self);
|
|
||||||
|
PlaneScan scan;
|
||||||
|
scan.set_plane(aqback.plane());
|
||||||
|
scan.set_bbox_given_center_radius(aqback.xyz(), LS.cknumber(lradius));
|
||||||
|
scan.set_shape(PlaneScan::SPHEROID);
|
||||||
|
scan.set_sorted(true);
|
||||||
|
scan.set_special(tan->id(), LS.ckboolean(lomit_self));
|
||||||
|
scan.set_omit_nowhere(LS.ckboolean(lomit_nowhere));
|
||||||
|
|
||||||
|
util::IdVector idv = w->plane_map_.scan(scan);
|
||||||
tangible_getall(LS, list, idv);
|
tangible_getall(LS, list, idv);
|
||||||
return LS.result();
|
return LS.result();
|
||||||
}
|
}
|
||||||
@@ -282,12 +288,15 @@ LuaDefine(tangible_scan, "plane,x,y,radius,omit_nowhere",
|
|||||||
LuaRet list;
|
LuaRet list;
|
||||||
LuaStack LS(L, lplane, lx, ly, lradius, lomit_nowhere, list);
|
LuaStack LS(L, lplane, lx, ly, lradius, lomit_nowhere, list);
|
||||||
World *w = World::fetch_global_pointer(L);
|
World *w = World::fetch_global_pointer(L);
|
||||||
eng::string plane = LS.ckstring(lplane);
|
|
||||||
double x = LS.cknumber(lx);
|
PlaneScan scan;
|
||||||
double y = LS.cknumber(ly);
|
scan.set_plane(LS.ckstring(lplane));
|
||||||
double radius = LS.cknumber(lradius);
|
scan.set_bbox_given_center_radius(util::XYZ(LS.cknumber(lx), LS.cknumber(ly), 0), LS.cknumber(lradius));
|
||||||
bool omit_nowhere = LS.ckboolean(lomit_nowhere);
|
scan.set_shape(PlaneScan::SPHEROID);
|
||||||
util::IdVector idv = w->plane_map_.scan_radius(plane, x, y, radius, omit_nowhere, 0, false);
|
scan.set_sorted(true);
|
||||||
|
scan.set_omit_nowhere(LS.ckboolean(lomit_nowhere));
|
||||||
|
|
||||||
|
util::IdVector idv = w->plane_map_.scan(scan);
|
||||||
tangible_getall(LS, list, idv);
|
tangible_getall(LS, list, idv);
|
||||||
return LS.result();
|
return LS.result();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,26 +220,23 @@ void World::tangible_delete(int64_t id) {
|
|||||||
LS.result();
|
LS.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
util::IdVector World::get_near_unsorted(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player) const {
|
util::IdVector World::get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player, bool sorted) const {
|
||||||
const Tangible *player = tangible_get(player_id);
|
const Tangible *player = tangible_get(player_id);
|
||||||
if (player == nullptr) {
|
if (player == nullptr) {
|
||||||
return IdVector();
|
return IdVector();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find out where's the center of the world.
|
// Find out where the player is.
|
||||||
const AnimStep &aqback = player->anim_queue_.back();
|
const AnimStep &aqback = player->anim_queue_.back();
|
||||||
return plane_map_.scan_radius_unsorted(aqback.plane(), aqback.xyz().x, aqback.xyz().y, radius, exclude_nowhere, player_id, omit_player);
|
|
||||||
}
|
|
||||||
|
|
||||||
util::IdVector World::get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player) const {
|
PlaneScan scan;
|
||||||
const Tangible *player = tangible_get(player_id);
|
scan.set_plane(aqback.plane());
|
||||||
if (player == nullptr) {
|
scan.set_bbox_given_center_radius(aqback.xyz(), radius);
|
||||||
return IdVector();
|
scan.set_shape(PlaneScan::SPHEROID);
|
||||||
}
|
scan.set_sorted(sorted);
|
||||||
|
scan.set_omit_nowhere(exclude_nowhere);
|
||||||
// Find out where's the center of the world.
|
scan.set_special(player_id, omit_player);
|
||||||
const AnimStep &aqback = player->anim_queue_.back();
|
return plane_map_.scan(scan);
|
||||||
return plane_map_.scan_radius(aqback.plane(), aqback.xyz().x, aqback.xyz().y, radius, exclude_nowhere, player_id, omit_player);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
World::Redirects World::fetch_redirects() {
|
World::Redirects World::fetch_redirects() {
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
util::IdVector World::get_visible_union(int64_t actor_id, World *master) {
|
util::IdVector World::get_visible_union(int64_t actor_id, World *master) {
|
||||||
return util::sort_union_id_vectors(
|
return util::sort_union_id_vectors(
|
||||||
master->get_near_unsorted(actor_id, RadiusVisibility, true, false),
|
master->get_near(actor_id, RadiusVisibility, true, false, false),
|
||||||
get_near_unsorted(actor_id, RadiusVisibility, true, false));
|
get_near(actor_id, RadiusVisibility, true, false, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t World::patch_actor(StreamBuffer *sb, DebugCollector *dbc) {
|
int64_t World::patch_actor(StreamBuffer *sb, DebugCollector *dbc) {
|
||||||
@@ -169,7 +169,7 @@ void World::patch_luatabs(StreamBuffer *sb, DebugCollector *dbc) {
|
|||||||
int64_t actor_id = sb->read_int64();
|
int64_t actor_id = sb->read_int64();
|
||||||
util::HashValue closehash = sb->read_hashvalue();
|
util::HashValue closehash = sb->read_hashvalue();
|
||||||
int ncreate = sb->read_int32();
|
int ncreate = sb->read_int32();
|
||||||
util::IdVector closetans = get_near(actor_id, RadiusClose, true, false);
|
util::IdVector closetans = get_near(actor_id, RadiusClose, true, false, true);
|
||||||
assert(closehash == util::hash_id_vector(closetans));
|
assert(closehash == util::hash_id_vector(closetans));
|
||||||
number_lua_tables(closetans);
|
number_lua_tables(closetans);
|
||||||
create_new_tables(ncreate);
|
create_new_tables(ncreate);
|
||||||
@@ -183,8 +183,8 @@ void World::diff_luatabs(int64_t actor_id, World *master, StreamBuffer *xsb) {
|
|||||||
StreamBuffer tsb;
|
StreamBuffer tsb;
|
||||||
|
|
||||||
// Calculate the set of close tangibles.
|
// Calculate the set of close tangibles.
|
||||||
util::IdVector closetans = master->get_near(actor_id, RadiusClose, true, false);
|
util::IdVector closetans = master->get_near(actor_id, RadiusClose, true, false, true);
|
||||||
assert(get_near(actor_id, RadiusClose, true, false) == closetans);
|
assert(get_near(actor_id, RadiusClose, true, false, true) == closetans);
|
||||||
util::HashValue closehash = util::hash_id_vector(closetans);
|
util::HashValue closehash = util::hash_id_vector(closetans);
|
||||||
|
|
||||||
// Number and pair tables in the synchronous and master model.
|
// Number and pair tables in the synchronous and master model.
|
||||||
@@ -250,7 +250,7 @@ void World::diff_tanclass(int64_t actor_id, World *master, StreamBuffer *xsb) {
|
|||||||
|
|
||||||
// Calculate the set of close tangibles.
|
// Calculate the set of close tangibles.
|
||||||
// TODO: we've already calculated this in an earlier function. This is wasteful.
|
// TODO: we've already calculated this in an earlier function. This is wasteful.
|
||||||
util::IdVector closetans = master->get_near(actor_id, RadiusClose, true, false);
|
util::IdVector closetans = master->get_near(actor_id, RadiusClose, true, false, true);
|
||||||
|
|
||||||
tsb.write_int32(0);
|
tsb.write_int32(0);
|
||||||
int write_count_after = tsb.total_writes();
|
int write_count_after = tsb.total_writes();
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ eng::string World::tangible_ids_debug_string() const {
|
|||||||
|
|
||||||
eng::string World::tangibles_near_debug_string(int64_t actor, int64_t distance) {
|
eng::string World::tangibles_near_debug_string(int64_t actor, int64_t distance) {
|
||||||
eng::ostringstream result;
|
eng::ostringstream result;
|
||||||
for (int64_t id : get_near(actor, distance, true, false)) {
|
for (int64_t id : get_near(actor, distance, true, false, true)) {
|
||||||
const Tangible *tan = tangible_get(id);
|
const Tangible *tan = tangible_get(id);
|
||||||
const AnimStep &aqback = tan->anim_queue_.back();
|
const AnimStep &aqback = tan->anim_queue_.back();
|
||||||
result << id << ": " << aqback.graphic() << " " << aqback.plane() << " " << aqback.xyz().debug_string() << std::endl;
|
result << id << ": " << aqback.graphic() << " " << aqback.plane() << " " << aqback.xyz().debug_string() << std::endl;
|
||||||
|
|||||||
@@ -122,10 +122,10 @@ public:
|
|||||||
//
|
//
|
||||||
// Get a list of the tangibles that are near the player. If 'exclude_nowhere' is
|
// Get a list of the tangibles that are near the player. If 'exclude_nowhere' is
|
||||||
// true, exclude any tangibles on the nowhere plane (but still include the player himself).
|
// true, exclude any tangibles on the nowhere plane (but still include the player himself).
|
||||||
// The unsorted version returns the tangibles in an unpredictable order.
|
// The unsorted version returns the tangibles in an unpredictable order. If sorted
|
||||||
|
// is false, return them in an unpredictable order.
|
||||||
//
|
//
|
||||||
IdVector get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player) const;
|
IdVector get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player, bool sorted) const;
|
||||||
IdVector get_near_unsorted(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player) const;
|
|
||||||
|
|
||||||
// Make a tangible.
|
// Make a tangible.
|
||||||
//
|
//
|
||||||
|
|||||||
14
luprex/core/wrap/wrap-bytell-hash-map.hpp
Normal file
14
luprex/core/wrap/wrap-bytell-hash-map.hpp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef WRAP_BYTELL_HASH_MAP_HPP
|
||||||
|
#define WRAP_BYTELL_HASH_MAP_HPP
|
||||||
|
|
||||||
|
#include "eng-malloc.hpp"
|
||||||
|
#include "bytell-hash-map.hpp"
|
||||||
|
|
||||||
|
namespace eng {
|
||||||
|
template<class K, class V, class H=std::hash<K>, class E=std::equal_to<K>>
|
||||||
|
class bytell_hash_map : public ska::bytell_hash_map<K, V, H, E, eng::allocator<std::pair<const K, V>>>, public eng::opnew {
|
||||||
|
using ska::bytell_hash_map<K, V, H, E, eng::allocator<std::pair<const K, V>>>::bytell_hash_map;
|
||||||
|
};
|
||||||
|
} // namespace eng
|
||||||
|
|
||||||
|
#endif // WRAP_BYTELL_HASH_MAP_HPP
|
||||||
Reference in New Issue
Block a user