mirror of
https://github.com/PixarAnimationStudios/OpenSubdiv
synced 2024-12-28 10:41:09 +00:00
Change container get methods (HbrMesh::GetVertices, etc) to take
output iterators instead of std::vector - eases prman integration for cases where std::list makes more sense. Block allocate face children array in common case (<= 4 children). Other, minor consistency edits.
This commit is contained in:
parent
2bbf5fb27e
commit
59edf56416
@ -115,6 +115,33 @@ inline bool operator< (const HbrFacePath& x, const HbrFacePath& y) {
|
||||
}
|
||||
}
|
||||
|
||||
// A simple wrapper around an array of four children. Used to block
|
||||
// allocate pointers to children of HbrFace in the common case
|
||||
template <class T>
|
||||
class HbrFaceChildren {
|
||||
public:
|
||||
HbrFace<T> *& operator[](const int index) {
|
||||
return children[index];
|
||||
}
|
||||
|
||||
const HbrFace<T> *& operator[](const int index) const {
|
||||
return children[index];
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
friend class HbrAllocator<HbrFaceChildren<T> >;
|
||||
|
||||
// Used by block allocator
|
||||
HbrFaceChildren<T>*& GetNext() { return (HbrFaceChildren<T>*&) children; }
|
||||
|
||||
HbrFaceChildren() {}
|
||||
|
||||
~HbrFaceChildren() {}
|
||||
|
||||
HbrFace<T> *children[4];
|
||||
};
|
||||
|
||||
template <class T> class HbrFace {
|
||||
|
||||
private:
|
||||
@ -161,8 +188,13 @@ public:
|
||||
|
||||
// Return the child with the indicated index
|
||||
HbrFace<T>* GetChild(int index) const {
|
||||
if (!children || index < 0 || index >= mesh->GetSubdivision()->GetFaceChildrenCount(nvertices)) return 0;
|
||||
return children[index];
|
||||
int nchildren = mesh->GetSubdivision()->GetFaceChildrenCount(nvertices);
|
||||
if (!children.children || index < 0 || index >= nchildren) return 0;
|
||||
if (nchildren > 4) {
|
||||
return children.extrachildren[index];
|
||||
} else {
|
||||
return (*children.children)[index];
|
||||
}
|
||||
}
|
||||
|
||||
// Subdivide the face into a vertex if needed and return
|
||||
@ -265,10 +297,19 @@ public:
|
||||
const HbrFace<T>* f = this, *p = GetParent();
|
||||
while (p) {
|
||||
int nchildren = mesh->GetSubdivision()->GetFaceChildrenCount(p->nvertices);
|
||||
for (int i = 0; i < nchildren; ++i) {
|
||||
if (p->children[i] == f) {
|
||||
path.remainder.push_back(i);
|
||||
break;
|
||||
if (nchildren > 4) {
|
||||
for (int i = 0; i < nchildren; ++i) {
|
||||
if (p->children.extrachildren[i] == f) {
|
||||
path.remainder.push_back(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < nchildren; ++i) {
|
||||
if ((*p->children.children)[i] == f) {
|
||||
path.remainder.push_back(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
f = p;
|
||||
@ -312,7 +353,7 @@ private:
|
||||
int nvertices;
|
||||
|
||||
// Halfedge array for this face
|
||||
// HbrHalfedge::GetIndex() relies on this being size 4
|
||||
// HbrHalfedge::getIndex() relies on this being size 4
|
||||
HbrHalfedge<T> edges[4];
|
||||
|
||||
// Edge storage if this face is not a triangle or quad
|
||||
@ -321,8 +362,13 @@ private:
|
||||
// Pointer to parent face
|
||||
HbrFace<T>* parent;
|
||||
|
||||
// Children (pointer) array
|
||||
HbrFace<T>** children;
|
||||
// Pointer to children array. If there are four children or less,
|
||||
// we use the HbrFaceChildren pointer, otherwise we use
|
||||
// extrachildren
|
||||
union {
|
||||
HbrFaceChildren<T>* children;
|
||||
HbrFace<T>** extrachildren;
|
||||
} children;
|
||||
|
||||
// Subdivided vertex child
|
||||
HbrVertex<T>* vchild;
|
||||
@ -368,12 +414,13 @@ namespace OPENSUBDIV_VERSION {
|
||||
|
||||
template <class T>
|
||||
HbrFace<T>::HbrFace()
|
||||
: mesh(0), id(-1), uindex(-1), ptexindex(-1), nvertices(0), extraedges(0), parent(0), children(0), vchild(0), fvarbits(0),
|
||||
: mesh(0), id(-1), uindex(-1), ptexindex(-1), nvertices(0), extraedges(0), parent(0), vchild(0), fvarbits(0),
|
||||
#ifdef HBRSTITCH
|
||||
stitchEdges(0),
|
||||
stitchDatas(0),
|
||||
#endif
|
||||
edits(0), clientData(0), depth(0), hole(0), coarse(0), protect(0), collected(0), hasVertexEdits(0), initialized(0), destroyed(0) {
|
||||
children.children = 0;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@ -385,7 +432,7 @@ HbrFace<T>::Initialize(HbrMesh<T>* m, HbrFace<T>* _parent, int childindex, int f
|
||||
ptexindex = -1;
|
||||
nvertices = nv;
|
||||
extraedges = 0;
|
||||
children = 0;
|
||||
children.children = 0;
|
||||
vchild = 0;
|
||||
fvarbits = 0;
|
||||
#ifdef HBRSTITCH
|
||||
@ -434,7 +481,7 @@ HbrFace<T>::Initialize(HbrMesh<T>* m, HbrFace<T>* _parent, int childindex, int f
|
||||
}
|
||||
|
||||
// We also ignore the edge array and allocate extra storage -
|
||||
// this simplifies GetNext and GetPrev math in HbrHalfede
|
||||
// this simplifies GetNext and GetPrev math in HbrHalfedge
|
||||
extraedges = new HbrHalfedge<T>[nv];
|
||||
|
||||
} else {
|
||||
@ -501,16 +548,27 @@ HbrFace<T>::Destroy() {
|
||||
#endif
|
||||
|
||||
// Remove children's references to self
|
||||
if (children) {
|
||||
if (children.children) {
|
||||
int nchildren = mesh->GetSubdivision()->GetFaceChildrenCount(nvertices);
|
||||
for (i = 0; i < nchildren; ++i) {
|
||||
if (children[i]) {
|
||||
children[i]->parent = 0;
|
||||
children[i] = 0;
|
||||
if (nchildren > 4) {
|
||||
for (i = 0; i < nchildren; ++i) {
|
||||
if (children.extrachildren[i]) {
|
||||
children.extrachildren[i]->parent = 0;
|
||||
children.extrachildren[i] = 0;
|
||||
}
|
||||
}
|
||||
delete[] children.extrachildren;
|
||||
children.extrachildren = 0;
|
||||
} else {
|
||||
for (i = 0; i < nchildren; ++i) {
|
||||
if ((*children.children)[i]) {
|
||||
(*children.children)[i]->parent = 0;
|
||||
(*children.children)[i] = 0;
|
||||
}
|
||||
}
|
||||
mesh->DeleteFaceChildren(children.children);
|
||||
children.children = 0;
|
||||
}
|
||||
}
|
||||
delete[] children;
|
||||
children = 0;
|
||||
}
|
||||
|
||||
// Deleting the incident edges from the vertices in this way is
|
||||
@ -542,20 +600,36 @@ HbrFace<T>::Destroy() {
|
||||
// Remove parent's reference to self
|
||||
if (parent) {
|
||||
bool parentHasOtherKids = false;
|
||||
assert(parent->children);
|
||||
int nchildren = mesh->GetSubdivision()->GetFaceChildrenCount(parent->nvertices);
|
||||
for (i = 0; i < nchildren; ++i) {
|
||||
if (parent->children[i] == this) {
|
||||
parent->children[i] = 0;
|
||||
} else if (parent->children[i]) parentHasOtherKids = true;
|
||||
}
|
||||
// After cleaning the parent's reference to self, the parent
|
||||
// may be able to clean itself up
|
||||
if (!parentHasOtherKids) {
|
||||
delete[] parent->children;
|
||||
parent->children = 0;
|
||||
if (parent->GarbageCollectable()) {
|
||||
mesh->DeleteFace(parent);
|
||||
if (nchildren > 4) {
|
||||
for (i = 0; i < nchildren; ++i) {
|
||||
if (parent->children.extrachildren[i] == this) {
|
||||
parent->children.extrachildren[i] = 0;
|
||||
} else if (parent->children.extrachildren[i]) parentHasOtherKids = true;
|
||||
}
|
||||
// After cleaning the parent's reference to self, the parent
|
||||
// may be able to clean itself up
|
||||
if (!parentHasOtherKids) {
|
||||
delete[] parent->children.extrachildren;
|
||||
parent->children.extrachildren = 0;
|
||||
if (parent->GarbageCollectable()) {
|
||||
mesh->DeleteFace(parent);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < nchildren; ++i) {
|
||||
if ((*parent->children.children)[i] == this) {
|
||||
(*parent->children.children)[i] = 0;
|
||||
} else if ((*parent->children.children)[i]) parentHasOtherKids = true;
|
||||
}
|
||||
// After cleaning the parent's reference to self, the parent
|
||||
// may be able to clean itself up
|
||||
if (!parentHasOtherKids) {
|
||||
mesh->DeleteFaceChildren(parent->children.children);
|
||||
parent->children.children = 0;
|
||||
if (parent->GarbageCollectable()) {
|
||||
mesh->DeleteFace(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
parent = 0;
|
||||
@ -622,16 +696,27 @@ HbrFace<T>::GetVertex(int index) const {
|
||||
template <class T>
|
||||
void
|
||||
HbrFace<T>::SetChild(int index, HbrFace<T>* face) {
|
||||
// Construct the children array if it doesn't already exist
|
||||
int i;
|
||||
if (!children) {
|
||||
int nchildren = mesh->GetSubdivision()->GetFaceChildrenCount(nvertices);
|
||||
children = new HbrFace<T>*[nchildren];
|
||||
for (i = 0; i < nchildren; ++i) {
|
||||
children[i] = 0;
|
||||
int nchildren = mesh->GetSubdivision()->GetFaceChildrenCount(nvertices);
|
||||
// Construct the children array if it doesn't already exist
|
||||
if (!children.children) {
|
||||
int i;
|
||||
if (nchildren > 4) {
|
||||
children.extrachildren = new HbrFace<T>*[nchildren];
|
||||
for (i = 0; i < nchildren; ++i) {
|
||||
children.extrachildren[i] = 0;
|
||||
}
|
||||
} else {
|
||||
children.children = mesh->NewFaceChildren();
|
||||
for (i = 0; i < nchildren; ++i) {
|
||||
(*children.children)[i] = 0;
|
||||
}
|
||||
}
|
||||
children[index] = face;
|
||||
}
|
||||
if (nchildren > 4) {
|
||||
children.extrachildren[index] = face;
|
||||
} else {
|
||||
(*children.children)[index] = face;
|
||||
}
|
||||
face->parent = this;
|
||||
}
|
||||
|
||||
@ -738,7 +823,7 @@ HbrFace<T>::ClearUsage() {
|
||||
template <class T>
|
||||
bool
|
||||
HbrFace<T>::GarbageCollectable() const {
|
||||
if (children || protect) return false;
|
||||
if (children.children || protect) return false;
|
||||
for (int i = 0; i < nvertices; ++i) {
|
||||
HbrHalfedge<T>* edge = GetEdge(i);
|
||||
HbrVertex<T>* vertex = edge->GetOrgVertex();
|
||||
|
@ -57,7 +57,7 @@
|
||||
#ifndef HBRFVARDATA_H
|
||||
#define HBRFVARDATA_H
|
||||
|
||||
#include <string.h>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
|
||||
#include "../version.h"
|
||||
|
@ -380,10 +380,9 @@ private:
|
||||
HbrHalfedge<T>* opposite;
|
||||
HbrFace<T>* incidentFace;
|
||||
|
||||
// Index of incident vertex
|
||||
HbrVertex<T>* incidentVertex;
|
||||
|
||||
// Index of child vertex
|
||||
// Child vertex
|
||||
HbrVertex<T>* vchild;
|
||||
float sharpness;
|
||||
|
||||
|
@ -161,7 +161,7 @@ using namespace OPENSUBDIV_VERSION;
|
||||
} // end namespace OpenSubdiv
|
||||
|
||||
#include "../hbr/face.h"
|
||||
#include <string.h>
|
||||
#include <cstring>
|
||||
|
||||
namespace OpenSubdiv {
|
||||
namespace OPENSUBDIV_VERSION {
|
||||
|
@ -57,13 +57,14 @@
|
||||
#ifndef HBRMESH_H
|
||||
#define HBRMESH_H
|
||||
|
||||
#if PRMAN
|
||||
#ifdef PRMAN
|
||||
#include "libtarget/TgMalloc.h" // only for alloca
|
||||
#include "libtarget/TgThread.h"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <iostream>
|
||||
@ -138,14 +139,20 @@ public:
|
||||
// Ask for face with the indicated ID
|
||||
HbrFace<T>* GetFace(int id) const;
|
||||
|
||||
// Returns a collection of all vertices in the mesh
|
||||
void GetVertices(std::vector<HbrVertex<T>*>& vertices) const;
|
||||
// Returns a collection of all vertices in the mesh. This function
|
||||
// requires an output iterator; to get the vertices into a
|
||||
// std::vector, use GetVertices(std::back_inserter(myvector))
|
||||
template <typename OutputIterator>
|
||||
void GetVertices(OutputIterator vertices) const;
|
||||
|
||||
// Applies operator to all vertices
|
||||
void ApplyOperatorAllVertices(HbrVertexOperator<T> &op) const;
|
||||
|
||||
// Returns a collection of all faces in the mesh
|
||||
void GetFaces(std::vector<HbrFace<T>*>& faces) const;
|
||||
// Returns a collection of all faces in the mesh. This function
|
||||
// requires an output iterator; to get the faces into a
|
||||
// std::vector, use GetFaces(std::back_inserter(myvector))
|
||||
template <typename OutputIterator>
|
||||
void GetFaces(OutputIterator faces) const;
|
||||
|
||||
// Returns the subdivision method
|
||||
HbrSubdivision<T>* GetSubdivision() const { return subdivision; }
|
||||
@ -250,16 +257,36 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Whether the mesh is in "transient" mode, i.e. all
|
||||
// vertices/faces that are created should be deemed temporary
|
||||
// When mode is true, the mesh is put in a "transient" mode,
|
||||
// i.e. all subsequent intermediate vertices/faces that are
|
||||
// created by subdivision are deemed temporary. This transient
|
||||
// data can be entirely freed by a subsequent call to
|
||||
// FreeTransientData(). Essentially, the mesh is checkpointed and
|
||||
// restored. This is useful when space is at a premium and
|
||||
// subdivided results are cached elsewhere. On the other hand,
|
||||
// repeatedly putting the mesh in and out of transient mode and
|
||||
// performing the same evaluations comes at a significant compute
|
||||
// cost.
|
||||
void SetTransientMode(bool mode) {
|
||||
m_transientMode = mode;
|
||||
}
|
||||
|
||||
// Frees transient subdivision data; returns the mesh to a
|
||||
// checkpointed state prior to a call to SetTransientMode.
|
||||
void FreeTransientData();
|
||||
|
||||
// Create new face children block for use by HbrFace
|
||||
HbrFaceChildren<T>* NewFaceChildren() {
|
||||
return m_faceChildrenAllocator.Allocate();
|
||||
}
|
||||
|
||||
// Recycle face children block used by HbrFace
|
||||
void DeleteFaceChildren(HbrFaceChildren<T>* facechildren) {
|
||||
m_faceChildrenAllocator.Deallocate(facechildren);
|
||||
}
|
||||
|
||||
private:
|
||||
#if PRMAN
|
||||
#ifdef PRMAN
|
||||
// This code is intended to be shared with PRman which provides its own
|
||||
// TgSpinLock mutex. Other clients are responsible for providing a Mutex
|
||||
// object with public Lock() and Unlock() functions.
|
||||
@ -339,6 +366,9 @@ private:
|
||||
const size_t m_vertexSize;
|
||||
HbrAllocator<HbrVertex<T> > m_vertexAllocator;
|
||||
|
||||
// Allocator for face children blocks used by HbrFace
|
||||
HbrAllocator<HbrFaceChildren<T> > m_faceChildrenAllocator;
|
||||
|
||||
// Memory used by this mesh alone, plus all its faces and vertices
|
||||
size_t m_memory;
|
||||
|
||||
@ -405,6 +435,7 @@ HbrMesh<T>::HbrMesh(HbrSubdivision<T>* s, int _fvarcount, const int *_fvarindice
|
||||
sizeof(HbrHalfedge<T>*) + // for incidentEdges[1]
|
||||
totalfvarwidth * sizeof(float) + sizeof(HbrFVarData<T>)),
|
||||
m_vertexAllocator(&m_memory, 512, 0, 0, m_vertexSize),
|
||||
m_faceChildrenAllocator(&m_memory, 512, 0, 0),
|
||||
m_memory(0),
|
||||
m_numCoarseFaces(-1),
|
||||
hasVertexEdits(0),
|
||||
@ -683,7 +714,7 @@ HbrMesh<T>::Finish() {
|
||||
}
|
||||
|
||||
std::vector<HbrVertex<T>*> vertexlist;
|
||||
GetVertices(vertexlist);
|
||||
GetVertices(std::back_inserter(vertexlist));
|
||||
for (typename std::vector<HbrVertex<T>*>::iterator vi = vertexlist.begin();
|
||||
vi != vertexlist.end(); ++vi) {
|
||||
HbrVertex<T>* vertex = *vi;
|
||||
@ -835,13 +866,14 @@ HbrMesh<T>::GetFace(int id) const {
|
||||
}
|
||||
|
||||
template <class T>
|
||||
template <typename OutputIterator>
|
||||
void
|
||||
HbrMesh<T>::GetVertices(std::vector<HbrVertex<T>*>& lvertices) const {
|
||||
HbrMesh<T>::GetVertices(OutputIterator lvertices) const {
|
||||
m_mutex.Lock();
|
||||
for (int vi = 0; vi < nvsets; ++vi) {
|
||||
HbrVertex<T>** vset = vertices[vi];
|
||||
for (int i = 0; i < vsetsize; ++i) {
|
||||
if (vset[i]) lvertices.push_back(vset[i]);
|
||||
if (vset[i]) *lvertices++ = vset[i];
|
||||
}
|
||||
}
|
||||
m_mutex.Unlock();
|
||||
@ -861,10 +893,11 @@ HbrMesh<T>::ApplyOperatorAllVertices(HbrVertexOperator<T> &op) const {
|
||||
}
|
||||
|
||||
template <class T>
|
||||
template <typename OutputIterator>
|
||||
void
|
||||
HbrMesh<T>::GetFaces(std::vector<HbrFace<T>*>& lfaces) const {
|
||||
HbrMesh<T>::GetFaces(OutputIterator lfaces) const {
|
||||
for (int i = 0; i < nfaces; ++i) {
|
||||
if (faces[i]) lfaces.push_back(faces[i]);
|
||||
if (faces[i]) *lfaces++ = faces[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -254,7 +254,7 @@ HbrSubdivision<T>::SubdivideCreaseWeight(HbrHalfedge<T>* edge, HbrVertex<T>* ver
|
||||
// the vertex (other than this crease edge)
|
||||
std::vector<HbrHalfedge<T>*> edges;
|
||||
vertex->GuaranteeNeighbors();
|
||||
vertex->GetSurroundingEdges(edges);
|
||||
vertex->GetSurroundingEdges(std::back_inserter(edges));
|
||||
|
||||
int n = 0;
|
||||
for (typename std::vector<HbrHalfedge<T>*>::iterator ei = edges.begin(); ei != edges.end(); ++ei) {
|
||||
|
@ -219,18 +219,24 @@ public:
|
||||
// incident halfedge cycles)
|
||||
bool IsSingular() const { return nIncidentEdges > 1; }
|
||||
|
||||
// Collect the ring of edges around this vertex. Note well:
|
||||
// not all edges in this list will have an orientation where
|
||||
// the origin of the edge is this vertex!
|
||||
void GetSurroundingEdges(std::vector<HbrHalfedge<T>*>& edges) const;
|
||||
// Collect the ring of edges around this vertex. Note well: not
|
||||
// all edges in this list will have an orientation where the
|
||||
// origin of the edge is this vertex! This function requires an
|
||||
// output iterator; to get the edges into a std::vector, use
|
||||
// GetSurroundingEdges(std::back_inserter(myvector))
|
||||
template <typename OutputIterator>
|
||||
void GetSurroundingEdges(OutputIterator edges) const;
|
||||
|
||||
// Apply an edge operator to each edge in the ring of edges
|
||||
// around this vertex
|
||||
void ApplyOperatorSurroundingEdges(HbrHalfedgeOperator<T> &op) const;
|
||||
|
||||
// Collect the ring of vertices around this vertex (the ones
|
||||
// that share an edge with this vertex)
|
||||
void GetSurroundingVertices(std::vector<HbrVertex<T>*>& vertices) const;
|
||||
// Collect the ring of vertices around this vertex (the ones that
|
||||
// share an edge with this vertex). This function requires an
|
||||
// output iterator; to get the vertices into a std::vector, use
|
||||
// GetSurroundingVertices(std::back_inserter(myvector))
|
||||
template <typename OutputIterator>
|
||||
void GetSurroundingVertices(OutputIterator vertices) const;
|
||||
|
||||
// Apply a vertex operator to each vertex in the ring of vertices
|
||||
// around this vertex
|
||||
@ -1286,18 +1292,19 @@ HbrVertex<T>::GetFractionalMask() const {
|
||||
}
|
||||
|
||||
template <class T>
|
||||
template <typename OutputIterator>
|
||||
void
|
||||
HbrVertex<T>::GetSurroundingEdges(std::vector<HbrHalfedge<T>*>& edges) const {
|
||||
HbrVertex<T>::GetSurroundingEdges(OutputIterator edges) const {
|
||||
HbrHalfedge<T>* start = GetIncidentEdge(), *edge, *next;
|
||||
edge = start;
|
||||
while (edge) {
|
||||
edges.push_back(edge);
|
||||
*edges++ = edge;
|
||||
next = GetNextEdge(edge);
|
||||
if (next == start) {
|
||||
break;
|
||||
} else if (!next) {
|
||||
// Special case for the last edge in a cycle.
|
||||
edges.push_back(edge->GetPrev());
|
||||
*edges++ = edge->GetPrev();
|
||||
break;
|
||||
} else {
|
||||
edge = next;
|
||||
@ -1325,12 +1332,13 @@ HbrVertex<T>::ApplyOperatorSurroundingEdges(HbrHalfedgeOperator<T> &op) const {
|
||||
}
|
||||
|
||||
template <class T>
|
||||
template <typename OutputIterator>
|
||||
void
|
||||
HbrVertex<T>::GetSurroundingVertices(std::vector<HbrVertex<T>*>& vertices) const {
|
||||
HbrVertex<T>::GetSurroundingVertices(OutputIterator vertices) const {
|
||||
HbrHalfedge<T>* start = GetIncidentEdge(), *edge, *next;
|
||||
edge = start;
|
||||
while (edge) {
|
||||
vertices.push_back(edge->GetDestVertex());
|
||||
*vertices++ = edge->GetDestVertex();
|
||||
next = GetNextEdge(edge);
|
||||
if (next == start) {
|
||||
break;
|
||||
@ -1338,7 +1346,7 @@ HbrVertex<T>::GetSurroundingVertices(std::vector<HbrVertex<T>*>& vertices) const
|
||||
// Special case for the last edge in a cycle: the last
|
||||
// vertex on that cycle is not the destination of an
|
||||
// outgoing halfedge
|
||||
vertices.push_back(edge->GetPrev()->GetOrgVertex());
|
||||
*vertices++ = edge->GetPrev()->GetOrgVertex();
|
||||
break;
|
||||
} else {
|
||||
edge = next;
|
||||
|
Loading…
Reference in New Issue
Block a user