All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tinfour.edge.EdgePool Maven / Gradle / Ivy

There is a newer version: 2.1.7
Show newest version
/*
 * Copyright 2015 Gary W. Lucas.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


/*
 * -----------------------------------------------------------------------
 *
 * Revision History:
 * Date     Name         Description
 * ------   ---------    -------------------------------------------------
 * 06/2015  G. Lucas     Adapted from ProtoTIN implementation of TriangleManager
 * 03/2017  G. Lucas     Moved to public scope
 *
 * Notes:
 *  The memory in this container is organized into pages, each page
 * holding a fixed number of Edges.   Some of the Edges are
 * committed to the TIN, others are in an "available state".  The pages
 * include links so that the container can maintain a single-direction linked
 * list of pages which have at least one QuadEdge in the "available" state.
 *
 * By design, the class guarantees that ALWAYS at least one page in an available
 * state.  This guarantee allows us to shave one conditional operation each
 * time a QuadEdge is inserted:
 *
 *    With guarantee:
 *       1) Add a QuadEdge to the page.
 *       2) Check to see if the page is full, if so add a new page
 *
 *    Without guarantee
 *       1) Check to see if there's an available page, if not add one
 *       2) Add QuadEdge to page
 *       3) Check to see if the page is full, if so add a new page
 *
 * The design of the class is based on the idea that Edges are added
 * and removed at random, but the number of Edges grows as the data
 * is processed. If this growth assumption is unfounded, then this class
 * would tend to end up with a lot of partially-populated pages
 *
 * The QuadEdge index
 *  The idea here is that the index element of a QuadEdge allows
 * the class to compute what page it belongs to.  So when a QuadEdge is
 * freed, it can modify the appropriate page.  However, there is a complication
 * in that we want the base reference for an edge and its dual to have
 * unique indices (for consistency with the SemiVirtualEdge classes).  So
 * the index for a distinct edge is multiplied by 2.  Thus, when trying to
 * relate an edge to a page, the page is identified by dividing the index
 * by 2.
 *--------------------------------------------------------------------------
 */
package org.tinfour.edge;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.tinfour.common.IQuadEdge;
import org.tinfour.common.Vertex;

/**
 * Provides an object-pool implementation that the manages
 * the allocation, deletion, and reuse of Edges.
 * 

This class is written using a very old-school approach as a way of * minimizing the frequency with which objects are garbage collected. Edges * are extensively allocated and freed as the TIN is built. Were they simply * constructed and put out-of-scope, the resulting garbage collection could * degrade performance. *

Note that this class is not thread safe. *

For performance reasons, many of the methods in this class make the * assumption that any edges passed into the method are under the management * of the current instance. If this assumption is violated, serious * errors could occur. For example, if an application uses one edge pool * to allocate an edge and then passes it to the deallocEdge method * another edge pool instance, both instances could become seriously * corrupted. */ @SuppressWarnings("PMD.AvoidArrayLoops") public class EdgePool implements Iterable { /** * The number of edges in an edge-pool page. */ private static final int EDGE_POOL_PAGE_SIZE = 1024; /** * The number of Edges stored in a page */ private final int pageSize; /** * The number of edge indices for a page, a value equal to pageSize*2; */ private final int pageSize2; Page[] pages; /** * The next page that includes available Edges. This reference is never * null. There is always at least one page with at least one free QuadEdge * in it. */ Page nextAvailablePage; int nAllocated; int nFree; int nAllocationOperations; int nFreeOperations; /** * Construct a QuadEdge manager allocating a small number * of initial edges. * */ public EdgePool() { this.pageSize = EDGE_POOL_PAGE_SIZE; this.pageSize2 = EDGE_POOL_PAGE_SIZE*2; pages = new Page[1]; pages[0] = new Page(0); nextAvailablePage = pages[0]; nextAvailablePage.initializeEdges(); nFree = pageSize; } /** * Gets the number of pages currently allocated. * * @return a value of 1 or greater. */ int getPageCount() { return pages.length; } /** * Get the number of edges allocated in a page * * @return a value of 1 or greater, usually 1024. */ int getPageSize() { return pageSize; } /** * Pre-allocates the specified number of edges. For a Delaunay * Triangulation with n vertices, there should be 3*n edges. * * @param n the number of edge (not vertices) to be allocated. */ public void preAllocateEdges(int n) { if (nFree >= n) { return; } int availablePageID = nextAvailablePage.pageID; int edgesNeeded = n - nFree; // number of new Edges needed int pagesNeeded = (edgesNeeded + pageSize - 1) / pageSize; int oldLen = pages.length; int nP = oldLen + pagesNeeded; pages = Arrays.copyOf(pages, nP); for (int i = oldLen; i < nP; i++) { pages[i] = new Page(i); // NOPMD pages[i].initializeEdges(); } for (int i = 0; i < nP - 1; i++) { pages[i].nextPage = pages[i + 1]; } nextAvailablePage = pages[availablePageID]; nFree += pagesNeeded * pageSize; } private void allocatePage() { int oldLength = pages.length; Page[] newPages = new Page[oldLength + 1]; System.arraycopy(pages, 0, newPages, 0, pages.length); newPages[oldLength] = new Page(oldLength); newPages[oldLength].initializeEdges(); pages = newPages; nFree += pageSize; nextAvailablePage = pages[oldLength]; for (int i = 0; i < pages.length - 1; i++) { pages[i].nextPage = pages[i + 1]; } } public QuadEdge allocateEdge(Vertex a, Vertex b) { Page page = nextAvailablePage; QuadEdge e = page.allocateEdge(); if (page.isFullyAllocated()) { nextAvailablePage = page.nextPage; if (nextAvailablePage == null) { allocatePage(); } } nFree--; nAllocated++; nAllocationOperations++; e.setVertices(a, b); return e; } /** * Allocates a QuadEdge with null vertices, assigning the responsibility * for populating the QuadEdge to the calling application. Because the * resulting QuadEdge is part of the QuadEdge collection, it is important * that the calling application fully populate the QuadEdge according * to its own processing requirements. * * @return a valid QuadEdge under the management of this collection. */ QuadEdge allocateUndefinedEdge() { Page page = nextAvailablePage; QuadEdge t = page.allocateEdge(); if (page.isFullyAllocated()) { nextAvailablePage = page.nextPage; if (nextAvailablePage == null) { allocatePage(); } } nFree--; nAllocated++; nAllocationOperations++; return t; } /** * Deallocates the QuadEdge returning it to the QuadEdge pool. * * @param e a valid QuadEdge */ public void deallocateEdge(QuadEdge e) { // Note: Although there is a sanity check method that can // be used to verify that the input edge belongs to this // edge pool, it is not used here for performance purposes. int iPage = e.getIndex() / pageSize2; Page page = pages[iPage]; if (page.isFullyAllocated()) { // since it will no longer be fully allocated, // add it to the linked list page.nextPage = nextAvailablePage; nextAvailablePage = page; } page.deallocateEdge(e); nAllocated--; nFree++; nFreeOperations++; } /** * Get the number of Edges currently stored in the collection * * @return an integer value of zero or more */ public int size() { return nAllocated; } /** * Get first valid, non-ghost QuadEdge in collection * * @return for a non-empty collection, a valid QuadEdge; otherwise a null */ public QuadEdge getStartingEdge() { for (Page p : pages) { if (p.nAllocated > 0) { for (int i = 0; i < p.nAllocated; i++) { if (p.edges[i].getB() != null && p.edges[i].getA() != null) { return p.edges[i]; } } } } return null; } public QuadEdge getStartingGhostEdge() { for (Page p : pages) { if (p.nAllocated > 0) { for (int i = 0; i < p.nAllocated; i++) { QuadEdge e = p.edges[i]; if (e.getB() == null) { return e; } } } } return null; } /** * Get a list of the Edges currently stored in the collection * * @return a valid, potentially empty list of edges */ public List getEdges() { ArrayList eList = new ArrayList<>(nAllocated); for (Page p : pages) { for (int j = 0; j < p.nAllocated; j++) { eList.add(p.edges[j]); } } return eList; } public int getEdgeCount() { return nAllocated; } /** * Puts all references used in the collection out-of-scope as a way of * simplifying and expediting garbage collection. */ public void dispose() { nextAvailablePage = null; for (int i = 0; i < pages.length; i++) { Page page = pages[i]; page.nextPage = null; for (int j = 0; j < pageSize; j++) { QuadEdge e = page.edges[j]; e.clear(); page.edges[j] = null; } page.edges = null; pages[i] = null; } pages = null; } /** * Deallocates all Edges, returning them to the free * list. Does not delete any existing objects. */ public void clear() { for (Page p : pages) { for (QuadEdge t : p.edges) { t.clear(); } p.nAllocated = 0; } nAllocated = 0; nFree = pages.length * pageSize; nAllocationOperations = 0; nFreeOperations = 0; this.nextAvailablePage = pages[0]; for (int i = 0; i < pages.length - 1; i++) { pages[i].nextPage = pages[i + 1]; } pages[pages.length - 1].nextPage = null; } @Override public String toString() { String s = "nEdges=" + nAllocated + ", nPages=" + pages.length + ", nFree=" + nFree; return s; } /** * Prints diagnostic information about the manager to the specified print * stream. * * @param ps a valid print stream. */ public void printDiagnostics(PrintStream ps) { int nPartials = 0; Page p = nextAvailablePage; while (p != null) { nPartials++; p = p.nextPage; } int nConstrained = 0; int nConstraintInterior = 0; int nConstraintBorder = 0; Iterator it = iterator(); while(it.hasNext()){ IQuadEdge e = it.next(); if(e.isConstrained()){ nConstrained++; if(e.isConstrainedRegionBorder()){ nConstraintBorder++; } }else if(e.isConstrainedRegionInterior()){ nConstraintInterior++; } } ps.format("Edges allocated: %8d%n", nAllocated); ps.format("Edges free: %8d%n", nFree); ps.format("Pages: %8d%n", pages.length); ps.format("Partially used pages: %8d%n", nPartials); ps.format("Total allocation operations: %8d%n", nAllocationOperations); ps.format("Total free operations: %8d%n", nFreeOperations); ps.format("Constrained edges %8d%n", nConstrained); ps.format(" Region borders: %8d%n", nConstraintBorder); ps.format(" Region interior: %8d%n", nConstraintInterior); } @Override public Iterator iterator() { return getIterator(true); } /** * Constructs an iterator that will optionally skip * ghost edges. * @param includeGhostEdges indicates that ghost edges are * to be included in the iterator production. * @return a valid instance of an iterator */ public Iterator getIterator(final boolean includeGhostEdges) { Iterator ix = new Iterator() { QuadEdge currentEdge; int nextPage; int nextEdge; boolean skipGhosts = !includeGhostEdges; boolean hasNext = findNextEdge(0, -1); private boolean findNextEdge(int iPage, int iEdge) { nextPage = iPage; nextEdge = iEdge; while (nextPage < pages.length) { nextEdge++; if (nextEdge < pages[nextPage].nAllocated) { if (skipGhosts) { IQuadEdge e = pages[nextPage].edges[nextEdge]; if (e.getA()==null || e.getB()==null) { continue; } } return true; } else { nextEdge = -1; nextPage++; } } return false; } @Override public boolean hasNext() { return hasNext; } /** * Overrides the default remove operation with an implementation that * throws an UnsupportedOperationException. Tinfour requires a specific * set of relationships between edges, and removing an edge from an * iterator would damage the overall structure and result in faulty * behavior. Therefore, Tinfour iterators do not support remove * operations. */ @Override public void remove() { throw new UnsupportedOperationException( "Remove operation not supported by this iterator"); } @Override public QuadEdge next() { currentEdge = null; if (hasNext) { currentEdge = pages[nextPage].edges[nextEdge]; hasNext = findNextEdge(nextPage, nextEdge); } return currentEdge; } }; return ix; } /** * Gets the maximum value of an edge index that is currently allocated * within the edge pool. * * @return a positive number or zero if the pool is currently unallocated. */ public int getMaximumAllocationIndex() { for (int iPage = pages.length - 1; iPage >= 0; iPage--) { Page p = pages[iPage]; if (p.nAllocated > 0) { return p.pageID * pageSize2 + p.nAllocated*2; } } return 0; } /** * Split the edge e into two by inserting a new vertex m into * the edge. The insertion point does not necessarily have to lie * on the segment. This method splits the segment into two segments * so that edge e(a,b) becomes edges p(a,m) and and e(m,b), * with forward and reverse links for both segments being adjusted * accordingly. The new segment p(a,m) is returned and the input segment * e is adjusted with new vertices (m,b). *

The split edge method preserves constraint flags and other attributes * associated with the edge. * @param e the input segment * @param m the insertion vertex * @return a valid instance of a QuadEdge or QuadEdgePartner (depending * on the class of the input) */ public QuadEdge splitEdge(QuadEdge e, Vertex m) { QuadEdge b = e.getBaseReference(); QuadEdge d = e.getDual(); QuadEdge eR = e.getReverse(); QuadEdge dF = d.getForward(); Vertex a = e.getA(); e.setA(m); QuadEdge p = this.allocateEdge(a, m); QuadEdge q = p.getDual(); p.setForward(e); p.setReverse(eR); q.setForward(dF); q.setReverse(d); // copy the constraint flags, if any p.dual.index = b.dual.index; // if (e instanceof QuadEdgePartner) { // return n.dual; // } else { // return n; // } return p; } private class Page { int pageID; int pageOffset; int nAllocated; QuadEdge[] edges; Page nextPage; Page(int pageID) { this.pageID = pageID; pageOffset = pageID * pageSize2; edges = new QuadEdge[pageSize]; } /** * Sets up the array of free Edges. This method is almost always * called when a new page is created. The only time it is not is in the * compact() operation where Edges will be shifted around. */ void initializeEdges() { for (int i = 0; i < pageSize; i++) { edges[i] = new QuadEdge(pageOffset + i*2); //NOPMD } } QuadEdge allocateEdge() { QuadEdge e = edges[nAllocated]; e.setIndex(pageID * pageSize2 + nAllocated*2); nAllocated++; return e; } /** * Free the QuadEdge for reuse, setting any external references to null, * but not damaging any arrays or management structures. *

* Note that it is important that deallocation set the * QuadEdge back to its initialization states. To conserve processing * the allocation routine assumes that any unused QuadEdge in * the collection is already in its initialized state and so doesn't * do any extra work. * * @param e a valid QuadEdge */ void deallocateEdge(QuadEdge be) { // reset to initialization state as necessary. // in this following block, we clear all flags that matter. // We also set any references to null to prevent // object retention and expedite garbage collection. // Note that the variable arrayIndex is NOT the edge index, // but rather the array index for the edge within the array of edge pairs // stored by this class. QuadEdge e = be.getBaseReference(); int arrayIndex = (e.getIndex() - pageOffset)/2; e.clear(); // The array of Edges must be kept // so that all allocated Edges are together at the beginning // of the array and all the free Edges are together at // the end of the array. If the removal // left a "hole" in the section of the array dedicated to allocated // Edges, shift Edges around, reassigning the managementID // of the QuadEdge that was shifted into the hole. nAllocated--; // nAllocated is now the index of the last allocated QuadEdge // in the array. We can modify the allocationID of that // QuadEdge and its position in the array because the // EdgeManager class is the only one that manipulates these // values. if (arrayIndex < nAllocated) { QuadEdge swap = edges[nAllocated]; edges[arrayIndex] = swap; swap.setIndex(pageOffset + arrayIndex*2); edges[nAllocated] = e; e.setIndex(pageOffset + nAllocated*2); // pro forma, for safety } } boolean isFullyAllocated() { return nAllocated == edges.length; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy