More type parametrization
This commit is contained in:
parent
44f9f29bf6
commit
4bd956e41f
@ -7,20 +7,19 @@
|
|||||||
|
|
||||||
namespace CosmoTool {
|
namespace CosmoTool {
|
||||||
|
|
||||||
template<int N>
|
template<int N, typename CType = ComputePrecision>
|
||||||
struct KDDef
|
struct KDDef
|
||||||
{
|
{
|
||||||
typedef float CoordType;
|
typedef CType CoordType;
|
||||||
typedef float KDCoordinates[N];
|
typedef float KDCoordinates[N];
|
||||||
static const int NumCubes = 1 << (N);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType = ComputePrecision>
|
||||||
struct KDCell
|
struct KDCell
|
||||||
{
|
{
|
||||||
bool active;
|
bool active;
|
||||||
ValType val;
|
ValType val;
|
||||||
typename KDDef<N>::KDCoordinates coord;
|
typename KDDef<N,CType>::KDCoordinates coord;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NotEnoughCells: public Exception
|
class NotEnoughCells: public Exception
|
||||||
@ -30,114 +29,113 @@ namespace CosmoTool {
|
|||||||
~NotEnoughCells() throw () {}
|
~NotEnoughCells() throw () {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType = ComputePrecision>
|
||||||
struct KDTreeNode
|
struct KDTreeNode
|
||||||
{
|
{
|
||||||
KDCell<N,ValType> *value;
|
KDCell<N,ValType,CType> *value;
|
||||||
KDTreeNode<N,ValType> *children[2];
|
KDTreeNode<N,ValType,CType> *children[2];
|
||||||
typename KDDef<N>::KDCoordinates minBound, maxBound;
|
typename KDDef<N,CType>::KDCoordinates minBound, maxBound;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType = ComputePrecision>
|
||||||
class RecursionInfoCells
|
class RecursionInfoCells
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
typename KDDef<N>::KDCoordinates x;
|
typename KDDef<N,CType>::KDCoordinates x;
|
||||||
typename KDDef<N>::CoordType r, r2;
|
typename KDDef<N,CType>::CoordType r, r2;
|
||||||
KDCell<N, ValType> **cells;
|
KDCell<N, ValType,CType> **cells;
|
||||||
typename KDDef<N>::CoordType *distances;
|
typename KDDef<N,CType>::CoordType *distances;
|
||||||
uint32_t currentRank;
|
uint32_t currentRank;
|
||||||
uint32_t numCells;
|
uint32_t numCells;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType = ComputePrecision>
|
||||||
class RecursionMultipleInfo
|
class RecursionMultipleInfo
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
const typename KDDef<N>::KDCoordinates& x;
|
const typename KDDef<N,CType>::KDCoordinates& x;
|
||||||
BoundedQueue< KDCell<N,ValType> *, typename KDDef<N>::CoordType> queue;
|
BoundedQueue< KDCell<N,ValType,CType> *, typename KDDef<N,CType>::CoordType> queue;
|
||||||
int traversed;
|
int traversed;
|
||||||
|
|
||||||
RecursionMultipleInfo(const typename KDDef<N>::KDCoordinates& rx,
|
RecursionMultipleInfo(const typename KDDef<N,CType>::KDCoordinates& rx,
|
||||||
KDCell<N,ValType> **cells,
|
KDCell<N,ValType,CType> **cells,
|
||||||
uint32_t numCells)
|
uint32_t numCells)
|
||||||
: x(rx), queue(cells, numCells, INFINITY),traversed(0)
|
: x(rx), queue(cells, numCells, INFINITY),traversed(0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType = ComputePrecision>
|
||||||
class KDTree
|
class KDTree
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static const int NumCubes = KDDef<N>::NumCubes;
|
typedef typename KDDef<N,CType>::CoordType CoordType;
|
||||||
|
|
||||||
public:
|
|
||||||
typedef typename KDDef<N>::CoordType CoordType;
|
|
||||||
typedef typename KDDef<N>::KDCoordinates coords;
|
typedef typename KDDef<N>::KDCoordinates coords;
|
||||||
|
typedef KDCell<N,ValType,CType> Cell;
|
||||||
|
typedef KDTreeNode<N,ValType,CType> Node;
|
||||||
|
|
||||||
KDTree(KDCell<N,ValType> *cells, uint32_t Ncells);
|
KDTree(Cell *cells, uint32_t Ncells);
|
||||||
~KDTree();
|
~KDTree();
|
||||||
|
|
||||||
uint32_t getIntersection(const coords& x, CoordType r,
|
uint32_t getIntersection(const coords& x, CoordType r,
|
||||||
KDCell<N, ValType> **cells,
|
Cell **cells,
|
||||||
uint32_t numCells)
|
uint32_t numCells)
|
||||||
throw (NotEnoughCells);
|
throw (NotEnoughCells);
|
||||||
uint32_t getIntersection(const coords& x, CoordType r,
|
uint32_t getIntersection(const coords& x, CoordType r,
|
||||||
KDCell<N, ValType> **cells,
|
Cell **cells,
|
||||||
CoordType *distances,
|
CoordType *distances,
|
||||||
uint32_t numCells)
|
uint32_t numCells)
|
||||||
throw (NotEnoughCells);
|
throw (NotEnoughCells);
|
||||||
|
|
||||||
KDCell<N, ValType> *getNearestNeighbour(const coords& x);
|
Cell *getNearestNeighbour(const coords& x);
|
||||||
|
|
||||||
void getNearestNeighbours(const coords& x, uint32_t N,
|
void getNearestNeighbours(const coords& x, uint32_t N,
|
||||||
KDCell<N, ValType> **cells);
|
Cell **cells);
|
||||||
void getNearestNeighbours(const coords& x, uint32_t N,
|
void getNearestNeighbours(const coords& x, uint32_t N,
|
||||||
KDCell<N, ValType> **cells,
|
Cell **cells,
|
||||||
CoordType *distances);
|
CoordType *distances);
|
||||||
|
|
||||||
KDTreeNode<N,ValType> *getRoot() { return root; }
|
Node *getRoot() { return root; }
|
||||||
|
|
||||||
void optimize();
|
void optimize();
|
||||||
|
|
||||||
KDTreeNode<N,ValType> *getAllNodes() { return nodes; }
|
Node *getAllNodes() { return nodes; }
|
||||||
uint32_t getNumNodes() const { return lastNode; }
|
uint32_t getNumNodes() const { return lastNode; }
|
||||||
|
|
||||||
uint32_t countActives() const;
|
uint32_t countActives() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
KDTreeNode<N, ValType> *nodes;
|
Node *nodes;
|
||||||
uint32_t numNodes;
|
uint32_t numNodes;
|
||||||
uint32_t lastNode;
|
uint32_t lastNode;
|
||||||
|
|
||||||
KDTreeNode<N, ValType> *root;
|
Node *root;
|
||||||
KDCell<N, ValType> **sortingHelper;
|
Cell **sortingHelper;
|
||||||
|
|
||||||
KDTreeNode<N, ValType> *buildTree(KDCell<N,ValType> **cell0,
|
Node *buildTree(Cell **cell0,
|
||||||
uint32_t N,
|
uint32_t N,
|
||||||
uint32_t depth,
|
uint32_t depth,
|
||||||
coords minBound,
|
coords minBound,
|
||||||
coords maxBound);
|
coords maxBound);
|
||||||
|
|
||||||
void recursiveIntersectionCells(RecursionInfoCells<N,ValType>& info,
|
void recursiveIntersectionCells(RecursionInfoCells<N,ValType, CType>& info,
|
||||||
KDTreeNode<N,ValType> *node,
|
Node *node,
|
||||||
int level)
|
int level)
|
||||||
throw (NotEnoughCells);
|
throw (NotEnoughCells);
|
||||||
|
|
||||||
CoordType computeDistance(KDCell<N,ValType> *cell, const coords& x);
|
CoordType computeDistance(Cell *cell, const coords& x);
|
||||||
void recursiveNearest(KDTreeNode<N, ValType> *node,
|
void recursiveNearest(Node *node,
|
||||||
int level,
|
int level,
|
||||||
const coords& x,
|
const coords& x,
|
||||||
CoordType& R2,
|
CoordType& R2,
|
||||||
KDCell<N,ValType>*& cell);
|
Cell*& cell);
|
||||||
void recursiveMultipleNearest(RecursionMultipleInfo<N,ValType>& info, KDTreeNode<N,ValType> *node,
|
void recursiveMultipleNearest(RecursionMultipleInfo<N,ValType,CType>& info, Node *node,
|
||||||
int level);
|
int level);
|
||||||
};
|
};
|
||||||
|
|
||||||
template<int N, class T>
|
template<int N, typename T, typename CType>
|
||||||
uint32_t gatherActiveCells(KDCell<N,T> **cells, uint32_t numCells);
|
uint32_t gatherActiveCells(KDCell<N,T,CType> **cells, uint32_t numCells);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
124
src/mykdtree.tcc
124
src/mykdtree.tcc
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
namespace CosmoTool {
|
namespace CosmoTool {
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType>
|
||||||
class CellCompare
|
class CellCompare
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -14,7 +14,7 @@ namespace CosmoTool {
|
|||||||
rank = k;
|
rank = k;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator()(const KDCell<N,ValType> *a, const KDCell<N,ValType> *b) const
|
bool operator()(const KDCell<N,ValType,CType> *a, const KDCell<N,ValType,CType> *b) const
|
||||||
{
|
{
|
||||||
return (a->coord[rank] < b->coord[rank]);
|
return (a->coord[rank] < b->coord[rank]);
|
||||||
}
|
}
|
||||||
@ -22,27 +22,27 @@ namespace CosmoTool {
|
|||||||
int rank;
|
int rank;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType>
|
||||||
KDTree<N,ValType>::~KDTree()
|
KDTree<N,ValType,CType>::~KDTree()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType>
|
||||||
KDTree<N,ValType>::KDTree(KDCell<N,ValType> *cells, uint32_t Ncells)
|
KDTree<N,ValType,CType>::KDTree(Cell *cells, uint32_t Ncells)
|
||||||
{
|
{
|
||||||
|
|
||||||
numNodes = Ncells;
|
numNodes = Ncells;
|
||||||
nodes = new KDTreeNode<N,ValType>[numNodes];
|
nodes = new Node[numNodes];
|
||||||
|
|
||||||
sortingHelper = new KDCell<N,ValType> *[Ncells];
|
sortingHelper = new Cell *[Ncells];
|
||||||
for (uint32_t i = 0; i < Ncells; i++)
|
for (uint32_t i = 0; i < Ncells; i++)
|
||||||
sortingHelper[i] = &cells[i];
|
sortingHelper[i] = &cells[i];
|
||||||
|
|
||||||
optimize();
|
optimize();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType>
|
||||||
void KDTree<N,ValType>::optimize()
|
void KDTree<N,ValType,CType>::optimize()
|
||||||
{
|
{
|
||||||
coords absoluteMin, absoluteMax;
|
coords absoluteMin, absoluteMax;
|
||||||
|
|
||||||
@ -62,13 +62,13 @@ namespace CosmoTool {
|
|||||||
std::cout << " done." << std::endl;
|
std::cout << " done." << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType>
|
||||||
uint32_t KDTree<N,ValType>::getIntersection(const coords& x, CoordType r,
|
uint32_t KDTree<N,ValType,CType>::getIntersection(const coords& x, CoordType r,
|
||||||
KDCell<N, ValType> **cells,
|
KDTree<N,ValType,CType>::Cell **cells,
|
||||||
uint32_t numCells)
|
uint32_t numCells)
|
||||||
throw (NotEnoughCells)
|
throw (NotEnoughCells)
|
||||||
{
|
{
|
||||||
RecursionInfoCells<N,ValType> info;
|
RecursionInfoCells<N,ValType,CType> info;
|
||||||
|
|
||||||
memcpy(info.x, x, sizeof(x));
|
memcpy(info.x, x, sizeof(x));
|
||||||
info.r = r;
|
info.r = r;
|
||||||
@ -82,11 +82,11 @@ namespace CosmoTool {
|
|||||||
return info.currentRank;
|
return info.currentRank;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType>
|
||||||
uint32_t KDTree<N,ValType>::getIntersection(const coords& x, CoordType r,
|
uint32_t KDTree<N,ValType,CType>::getIntersection(const coords& x, CoordType r,
|
||||||
KDCell<N, ValType> **cells,
|
Cell **cells,
|
||||||
CoordType *distances,
|
CoordType *distances,
|
||||||
uint32_t numCells)
|
uint32_t numCells)
|
||||||
throw (NotEnoughCells)
|
throw (NotEnoughCells)
|
||||||
{
|
{
|
||||||
RecursionInfoCells<N,ValType> info;
|
RecursionInfoCells<N,ValType> info;
|
||||||
@ -103,10 +103,10 @@ namespace CosmoTool {
|
|||||||
return info.currentRank;
|
return info.currentRank;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType>
|
||||||
void KDTree<N,ValType>::recursiveIntersectionCells(RecursionInfoCells<N,ValType>& info,
|
void KDTree<N,ValType,CType>::recursiveIntersectionCells(RecursionInfoCells<N,ValType,CType>& info,
|
||||||
KDTreeNode<N,ValType> *node,
|
Node *node,
|
||||||
int level)
|
int level)
|
||||||
throw (NotEnoughCells)
|
throw (NotEnoughCells)
|
||||||
{
|
{
|
||||||
int axis = level % N;
|
int axis = level % N;
|
||||||
@ -147,9 +147,9 @@ namespace CosmoTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType>
|
||||||
uint32_t gatherActiveCells(KDCell<N,ValType> **cells,
|
uint32_t gatherActiveCells(KDCell<N,ValType,CType> **cells,
|
||||||
uint32_t Ncells)
|
uint32_t Ncells)
|
||||||
{
|
{
|
||||||
uint32_t swapId = Ncells-1;
|
uint32_t swapId = Ncells-1;
|
||||||
uint32_t i = 0;
|
uint32_t i = 0;
|
||||||
@ -172,24 +172,24 @@ namespace CosmoTool {
|
|||||||
return swapId+1;
|
return swapId+1;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType>
|
||||||
KDTreeNode<N, ValType> *KDTree<N,ValType>::buildTree(KDCell<N,ValType> **cell0,
|
KDTreeNode<N,ValType,CType> *KDTree<N,ValType,CType>::buildTree(Cell **cell0,
|
||||||
uint32_t Ncells,
|
uint32_t Ncells,
|
||||||
uint32_t depth,
|
uint32_t depth,
|
||||||
coords minBound,
|
coords minBound,
|
||||||
coords maxBound)
|
coords maxBound)
|
||||||
{
|
{
|
||||||
if (Ncells == 0)
|
if (Ncells == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
int axis = depth % N;
|
int axis = depth % N;
|
||||||
KDTreeNode<N,ValType> *node = &nodes[lastNode++];
|
Node *node = &nodes[lastNode++];
|
||||||
uint32_t mid = Ncells/2;
|
uint32_t mid = Ncells/2;
|
||||||
coords tmpBound;
|
coords tmpBound;
|
||||||
|
|
||||||
// Isolate the environment
|
// Isolate the environment
|
||||||
{
|
{
|
||||||
CellCompare<N,ValType> compare(axis);
|
CellCompare<N,ValType,CType> compare(axis);
|
||||||
std::sort(cell0, cell0+Ncells, compare);
|
std::sort(cell0, cell0+Ncells, compare);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,8 +211,8 @@ namespace CosmoTool {
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType>
|
||||||
uint32_t KDTree<N,ValType>::countActives() const
|
uint32_t KDTree<N,ValType,CType>::countActives() const
|
||||||
{
|
{
|
||||||
uint32_t numActive = 0;
|
uint32_t numActive = 0;
|
||||||
for (uint32_t i = 0; i < lastNode; i++)
|
for (uint32_t i = 0; i < lastNode; i++)
|
||||||
@ -223,9 +223,9 @@ namespace CosmoTool {
|
|||||||
return numActive;
|
return numActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType>
|
||||||
typename KDDef<N>::CoordType
|
typename KDDef<N,CType>::CoordType
|
||||||
KDTree<N,ValType>::computeDistance(KDCell<N, ValType> *cell, const coords& x)
|
KDTree<N,ValType,CType>::computeDistance(Cell *cell, const coords& x)
|
||||||
{
|
{
|
||||||
CoordType d2 = 0;
|
CoordType d2 = 0;
|
||||||
|
|
||||||
@ -237,18 +237,18 @@ namespace CosmoTool {
|
|||||||
return d2;
|
return d2;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType>
|
||||||
void
|
void
|
||||||
KDTree<N,ValType>::recursiveNearest(
|
KDTree<N,ValType,CType>::recursiveNearest(
|
||||||
KDTreeNode<N,ValType> *node,
|
Node *node,
|
||||||
int level,
|
int level,
|
||||||
const coords& x,
|
const coords& x,
|
||||||
CoordType& R2,
|
CoordType& R2,
|
||||||
KDCell<N,ValType> *& best)
|
Cell *& best)
|
||||||
{
|
{
|
||||||
CoordType d2 = 0;
|
CoordType d2 = 0;
|
||||||
int axis = level % N;
|
int axis = level % N;
|
||||||
KDTreeNode<N,ValType> *other, *go;
|
Node *other, *go;
|
||||||
|
|
||||||
if (x[axis] < node->value->coord[axis])
|
if (x[axis] < node->value->coord[axis])
|
||||||
{
|
{
|
||||||
@ -311,26 +311,26 @@ namespace CosmoTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType>
|
||||||
KDCell<N, ValType> *
|
KDCell<N,ValType,CType> *
|
||||||
KDTree<N,ValType>::getNearestNeighbour(const coords& x)
|
KDTree<N,ValType,CType>::getNearestNeighbour(const coords& x)
|
||||||
{
|
{
|
||||||
CoordType R2 = INFINITY;
|
CoordType R2 = INFINITY;
|
||||||
KDCell<N,ValType> *best = 0;
|
Cell *best = 0;
|
||||||
|
|
||||||
recursiveNearest(root, 0, x, R2, best);
|
recursiveNearest(root, 0, x, R2, best);
|
||||||
|
|
||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType>
|
||||||
void
|
void
|
||||||
KDTree<N,ValType>::recursiveMultipleNearest(RecursionMultipleInfo<N,ValType>& info, KDTreeNode<N,ValType> *node,
|
KDTree<N,ValType,CType>::recursiveMultipleNearest(RecursionMultipleInfo<N,ValType,CType>& info, Node *node,
|
||||||
int level)
|
int level)
|
||||||
{
|
{
|
||||||
CoordType d2 = 0;
|
CoordType d2 = 0;
|
||||||
int axis = level % N;
|
int axis = level % N;
|
||||||
KDTreeNode<N,ValType> *other, *go;
|
Node *other, *go;
|
||||||
|
|
||||||
if (info.x[axis] < node->value->coord[axis])
|
if (info.x[axis] < node->value->coord[axis])
|
||||||
{
|
{
|
||||||
@ -380,9 +380,9 @@ namespace CosmoTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType>
|
||||||
void KDTree<N,ValType>::getNearestNeighbours(const coords& x, uint32_t N2,
|
void KDTree<N,ValType,CType>::getNearestNeighbours(const coords& x, uint32_t N2,
|
||||||
KDCell<N, ValType> **cells)
|
Cell **cells)
|
||||||
{
|
{
|
||||||
RecursionMultipleInfo<N,ValType> info(x, cells, N2);
|
RecursionMultipleInfo<N,ValType> info(x, cells, N2);
|
||||||
|
|
||||||
@ -391,13 +391,13 @@ namespace CosmoTool {
|
|||||||
|
|
||||||
recursiveMultipleNearest(info, root, 0);
|
recursiveMultipleNearest(info, root, 0);
|
||||||
|
|
||||||
std::cout << "Traversed = " << info.traversed << std::endl;
|
// std::cout << "Traversed = " << info.traversed << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<int N, typename ValType>
|
template<int N, typename ValType, typename CType>
|
||||||
void KDTree<N,ValType>::getNearestNeighbours(const coords& x, uint32_t N2,
|
void KDTree<N,ValType,CType>::getNearestNeighbours(const coords& x, uint32_t N2,
|
||||||
KDCell<N, ValType> **cells,
|
Cell **cells,
|
||||||
CoordType *distances)
|
CoordType *distances)
|
||||||
{
|
{
|
||||||
RecursionMultipleInfo<N,ValType> info(x, cells, N2);
|
RecursionMultipleInfo<N,ValType> info(x, cells, N2);
|
||||||
|
|
||||||
@ -405,9 +405,9 @@ namespace CosmoTool {
|
|||||||
cells[i] = 0;
|
cells[i] = 0;
|
||||||
|
|
||||||
recursiveMultipleNearest(info, root, 0);
|
recursiveMultipleNearest(info, root, 0);
|
||||||
memcpy(distances, info.getPriorities(), sizeof(CoordType)*N2);
|
memcpy(distances, info.queue.getPriorities(), sizeof(CoordType)*N2);
|
||||||
|
|
||||||
std::cout << "Traversed = " << info.traversed << std::endl;
|
// std::cout << "Traversed = " << info.traversed << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user