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

org.tinfour.semivirtual.SemiVirtualEdgePool Maven / Gradle / Ivy

/*
 * 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
 * ------   ---------    -------------------------------------------------
 * 10/2015  G. Lucas     Refactored from IncrementalTIN EdgePool
 *
 * 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
 *
 * Management ID
 *  The idea here is that the managementID 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.
 *--------------------------------------------------------------------------
 */
package org.tinfour.semivirtual;

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;
import static org.tinfour.semivirtual.SemiVirtualEdgePage.INDEX_MASK;
import static org.tinfour.semivirtual.SemiVirtualEdgePage.INDICES_PER_PAGE;
import static org.tinfour.semivirtual.SemiVirtualEdgePage.MASK_LOW_BIT_CLEAR;
import static org.tinfour.semivirtual.SemiVirtualEdgePage.PAIRS_PER_PAGE;

/**
 * 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. */ @SuppressWarnings("PMD.AvoidArrayLoops") class SemiVirtualEdgePool implements Iterable { SemiVirtualEdgePool self; SemiVirtualEdgePage[] 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. */ SemiVirtualEdgePage nextAvailablePage; int nAllocated; int nFree; int nAllocationOperations; int nFreeOperations; /** * Construct a Edge manager allocating a small number * of initial edges. * */ SemiVirtualEdgePool() { self = this; pages = new SemiVirtualEdgePage[1]; pages[0] = new SemiVirtualEdgePage(0); nextAvailablePage = pages[0]; nAllocated = 0; nFree = pages.length * PAIRS_PER_PAGE; } // private void reserveEdgeZero() { // pages[0].nAllocated = 1; // nAllocated = 1; // nFree--; // } /** * 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 INDICES_PER_PAGE; } /** * 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. */ void preAllocateEdges(int n) { if (nFree >= n) { return; } int availablePageID = nextAvailablePage.pageID; int edgesNeeded = n - nFree; // number of new Edges needed int pagesNeeded = (edgesNeeded + PAIRS_PER_PAGE - 1) / PAIRS_PER_PAGE; int oldLen = pages.length; int nP = oldLen + pagesNeeded; pages = Arrays.copyOf(pages, nP); for (int i = oldLen; i < nP; i++) { pages[i] = new SemiVirtualEdgePage(i); //NOPMD } for (int i = 0; i < nP - 1; i++) { pages[i].nextPage = pages[i + 1]; } nextAvailablePage = pages[availablePageID]; nFree += pagesNeeded * PAIRS_PER_PAGE; } private void allocatePage() { int oldLength = pages.length; SemiVirtualEdgePage[] newPages = new SemiVirtualEdgePage[oldLength + 1]; System.arraycopy(pages, 0, newPages, 0, pages.length); newPages[oldLength] = new SemiVirtualEdgePage(oldLength); pages = newPages; nFree += PAIRS_PER_PAGE; nextAvailablePage = pages[oldLength]; for (int i = 0; i < pages.length - 1; i++) { pages[i].nextPage = pages[i + 1]; } } SemiVirtualEdge allocateUnassignedEdge() { return new SemiVirtualEdge(this); } void allocateEdgeWithReceiver(SemiVirtualEdge receiver, Vertex a, Vertex b) { SemiVirtualEdgePage page = nextAvailablePage; int absIndex = page.allocateEdge(a, b); if (page.isFullyAllocated()) { nextAvailablePage = page.nextPage; if (nextAvailablePage == null) { allocatePage(); } } nFree--; nAllocated++; nAllocationOperations++; receiver.page = page; receiver.index = absIndex; receiver.indexOnPage = absIndex & INDEX_MASK; } SemiVirtualEdge allocateEdge(Vertex a, Vertex b) { SemiVirtualEdgePage page = nextAvailablePage; int absIndex = page.allocateEdge(a, b); if (page.isFullyAllocated()) { nextAvailablePage = page.nextPage; if (nextAvailablePage == null) { allocatePage(); } } nFree--; nAllocated++; nAllocationOperations++; return new SemiVirtualEdge(this, page, absIndex); } public SemiVirtualEdge getStartingEdge() { for (SemiVirtualEdgePage page : pages) { int[] allocatedEdges = page.getAllocations(); for (int j = 0; j < allocatedEdges.length; j++) { int iEdge = allocatedEdges[j]; int index = iEdge & INDEX_MASK; if (page.vertices[index] != null && page.vertices[index + 1] != null) { return new SemiVirtualEdge(this, page, iEdge); } } } return null; } public SemiVirtualEdge getStartingGhostEdge() { for (int i = 0; i < pages.length; i++) { SemiVirtualEdgePage page = pages[i]; int[] allocatedEdges = page.getAllocations(); for (int j = 0; j < allocatedEdges.length; j++) { int iEdge = allocatedEdges[j]; int index = iEdge & INDEX_MASK; if (page.vertices[index] != null && page.vertices[index + 1] == null) { return new SemiVirtualEdge(this, page, iEdge); } } } return null; } /** * Deallocates the QuadEdge returning it to the QuadEdge pool. * * @param e a valid QuadEdge */ void deallocateEdge(int absIndex) { int iPage = absIndex / INDICES_PER_PAGE; SemiVirtualEdgePage 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(absIndex); nAllocated--; nFree++; nFreeOperations++; } void deallocateEdge(SemiVirtualEdge e) { deallocateEdge(e.getIndex()); } SemiVirtualEdgePage getPageForIndex(int index) { return pages[index / INDICES_PER_PAGE]; } /** * Get the number of Edges currently stored in the collection * * @return an integer value of zero or more */ public int size() { return nAllocated; } /** * 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 (SemiVirtualEdgePage p : pages) { int[] map = p.getAllocations(); for (int i = 0; i < map.length; i++) { eList.add(new SemiVirtualEdge(this, p, map[i])); //NOPMD } } return eList; } public List getVirtualEdges() { ArrayList eList = new ArrayList<>(nAllocated); for (SemiVirtualEdgePage p : pages) { int[] map = p.getAllocations(); for (int i = 0; i < map.length; i++) { eList.add(new SemiVirtualEdge(this, p, map[i])); //NOPMD } } 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. */ void dispose() { nextAvailablePage = null; for (int i = 0; i < pages.length; i++) { pages[i].dispose(); pages[i].nextPage = null; pages[i] = null; } pages = null; } /** * Deallocates all Edges, returning them to the free * list. Does not delete any existing objects. */ void clear() { for (SemiVirtualEdgePage p : pages) { p.clear(); } 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; nAllocated = 0; nFree = pages.length * PAIRS_PER_PAGE; } @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; SemiVirtualEdgePage 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("%nEdge pool diagnostics%n"); 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); } /** * 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--) { SemiVirtualEdgePage p = pages[iPage]; if (p.nPairsAllocated > 0) { return (p.pageID + 1) * INDICES_PER_PAGE; } } return 0; } SemiVirtualEdge getEdgeForIndex(int index) { int iPage = index / INDICES_PER_PAGE; SemiVirtualEdgePage page = pages[iPage]; return new SemiVirtualEdge(this, page, index); } void getEdgeForIndexWithReceiver(final SemiVirtualEdge receiver, final int index, Vertex a, Vertex b) { int iPage = index / INDICES_PER_PAGE; SemiVirtualEdgePage page = pages[iPage]; receiver.page = page; receiver.index = index; receiver.indexOnPage = index & INDEX_MASK; int side = index & 1; int offset = index & (INDEX_MASK & MASK_LOW_BIT_CLEAR); page.vertices[offset | side] = a; page.vertices[offset | (side ^ 1)] = b; } /** * Provides an implementation of iterator which loops through * the virtual edge collection, returning the base-edges in order * (dual edges are not included). * * @return a valid iterator. */ @Override public Iterator iterator() { return getIterator(true); } /** * Constructs an iterator that will optionally skip ghost edges. * * @param includeGhostEdgess indicates that ghost edges are included. * @return a valid instance of an iterator */ Iterator getIterator(final boolean includeGhostEdges) { return new Iterator() { boolean skipGhostEdges = !includeGhostEdges; SemiVirtualEdgePool pool; int iEdge; int iPage; int[] map = pages[0].getAllocations(); boolean hasNext = processHasNext(0, -1); private boolean processHasNext(int pageIndex, int edgeIndex) { iPage = pageIndex; iEdge = edgeIndex; while (iPage < pages.length) { iEdge++; if (iEdge >= map.length) { iPage++; iEdge = -1; if (iPage == pages.length) { return false; } map = pages[iPage].getAllocations(); continue; } else { // there is an edge, do we need to test whether it's a ghost? if (skipGhostEdges) { int i = map[iEdge]&INDEX_MASK; Vertex[] v = pages[iPage].vertices; if (v[i] == null || v[i + 1] == null) { continue; } } return true; } } return false; } @Override public boolean hasNext() { return hasNext; } @Override public SemiVirtualEdge next() { if (!hasNext) { return null; } int index = map[iEdge]; SemiVirtualEdgePage page = pages[iPage]; SemiVirtualEdge e = new SemiVirtualEdge(self, page, index); hasNext = processHasNext(iPage, iEdge); return e; } /** * 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"); } }; } /** * 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 SemiVirtualEdge splitEdge(SemiVirtualEdge e, Vertex m) { //TO DO: The code where we set up links is just the same stuff // as the conventional edge pool. We may be able to improve // performance by just setting the links directly rather than // creating SemiVirtualEdge instances. However, Java is pretty // amazing at optimizing these days, and there is a good // chance that the saving will be small compared to the effort // of writing the code. SemiVirtualEdge d = e.getDual(); SemiVirtualEdge eR = e.getReverse(); SemiVirtualEdge dF = d.getForward(); Vertex a = e.getA(); e.setA(m); SemiVirtualEdge p = this.allocateEdge(a, m); SemiVirtualEdge q = p.getDual(); p.setForward(e); p.setReverse(eR); q.setForward(dF); q.setReverse(d); SemiVirtualEdgePage ePage = e.page; SemiVirtualEdgePage pPage = p.page; if (ePage.constraints == null) { return p; // we're done } pPage.readyConstraints(); // recall that constraints flags are shared between edge pairs // so we divide index by two int constraintFlags = ePage.constraints[e.indexOnPage / 2]; pPage.constraints[p.indexOnPage / 2] = constraintFlags; return p; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy