/* * ContiguousBlockAllocator.java * JCollider * * Copyright (c) 2004-2010 Hanns Holger Rutz. All rights reserved. * * This software is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either * version 2, june 1991 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License (gpl.txt) along with this software; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * For further information, please contact Hanns Holger Rutz at * contact@sciss.de , or visit http://www.sciss.de/jcollider * * * JCollider is closely modelled after SuperCollider Language, * often exhibiting a direct translation from Smalltalk to Java. * SCLang is a software originally developed by James McCartney, * which has become an Open Source project. * See http://www.audiosynth.com/ for details. * * * Changelog: * 24-Jul-06 created */ package de.sciss.jcollider; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * Quite a 1:1 translation from the SClang class written by James Harkins, this * class is a robust allocator for busses in a dynamic environment. * It does not exhibit the fragmentation problem of the PowerOfTwoAllocator. * * @author Hanns Holger Rutz * @version 0.32, 25-Feb-08 * * @todo freed should be a sorted Map !! then findAvailable could be faster * * @warning this class has not been thoroughly debugged */ public class ContiguousBlockAllocator implements BlockAllocator { private final int size; private final Block[] array; private final Map freed; private final int pos; private int top; public ContiguousBlockAllocator( int size ) { this( size, 0 ); } public ContiguousBlockAllocator( int size, int pos ) { this.size = size; this.pos = pos; array = new Block[ size ]; array[ pos ]= new Block( pos, size - pos ); freed = new HashMap(); top = pos; } public int alloc() { return alloc( 1 ); } public int alloc( int n ) { final Block b = findAvailable( n ); if( b == null ) return -1; return reserve( b.start, n, b, null ).start; } public Block reserve( int address ) { return reserve( address, 1 ); } public Block reserve( int address, int size ) { Block b; if( array[ address ] != null ) { b = array[ address ]; } else { b = findNext( address ); } if( (b != null) && b.used && (address + size > b.start) ) { throw new IllegalStateException( "The block at (" + address + ", " + size + ") is already in use and cannot be reserved." ); } if( b.start == address ) { return reserve( address, size, b, null ); } b = findPrevious( address ); if( (b != null) && b.used && (b.start + b.size > address) ) { throw new IllegalStateException( "The block at (" + address + ", " + size + ") is already in use and cannot be reserved." ); } return reserve( address, size, null, b ); } public void free( int address ) { Block b, prev, next, temp; b = array[ address ]; if( (b != null) && b.used ) { b.used = false; addToFreed( b ); prev = findPrevious( address ); if( (prev != null) && !prev.used ) { temp = prev.join( b ); if( temp != null ) { // if block is the last one, reduce the top if( b.start == top ) { top = temp.start; } array[ temp.start ] = temp; array[ b.start ] = null; removeFromFreed( prev ); removeFromFreed( b ); if( top > temp.start ) { addToFreed( temp ); } b = temp; } } next = findNext( b.start ); if( (next != null) && !next.used ) { temp = next.join( b ); if( temp != null ) { // if next is the last one, reduce the top if( next.start == top ) { top = temp.start; } array[ temp.start ] = temp; array[ next.start ] = null; removeFromFreed( next ); removeFromFreed( b ); } if( top > temp.start ) { addToFreed( temp ); } } } } public List getAllocatedBlocks() { final List result = new ArrayList(); Block b; for( int i = 0; i < array.length; i++ ) { b = array[ i ]; if( (b != null) && b.used ) { result.add( b ); } } return result; } private Block findAvailable( int n ) { Set set; Map.Entry entry; set = (Set) freed.get( new Integer( n )); if( set != null ) return (Block) set.iterator().next(); for( Iterator iter = freed.entrySet().iterator(); iter.hasNext(); ) { entry = (Map.Entry) iter.next(); if( ((Integer) entry.getKey()).intValue() >= n ) { set = (Set) entry.getValue(); if( set != null ) return (Block) set.iterator().next(); } } if( (top + n > size) || array[ top ].used ) return null; return array[ top ]; } private void addToFreed( Block b ) { final Object key = new Integer( b.size ); Set set; set = (Set) freed.get( key ); if( set == null ) { set = new HashSet(); freed.put( key, set ); } set.add( b ); } private void removeFromFreed( Block b ) { final Object key = new Integer( b.size ); final Set set = (Set) freed.get( key ); if( set != null ) { set.remove( b ); if( set.isEmpty() ) { freed.remove( key ); } } } private Block findPrevious( int address ) { for( int i = address - 1; i >= pos; i-- ) { if( array[ i ] != null ) return array[ i ]; } return null; } private Block findNext( int address ) { final Block temp = array[ address ]; if( temp != null ) return array[ temp.start + temp.size ]; for( int i = address + 1; i <= top; i++ ) { if( array[ i ] != null ) return array[ i ]; } return null; } private Block reserve( int address, int size, Block availBlock, Block prevBlock ) { if( availBlock == null ) { availBlock = prevBlock == null ? findPrevious( address ) : prevBlock; } if( availBlock.start < address ) { availBlock = split( availBlock, address - availBlock.start, false )[ 1 ]; } return split( availBlock, size, true )[ 0 ]; } private Block[] split( Block availBlock, int n, boolean used ) { final Block[] result = availBlock.split( n ); final Block newB = result[ 0 ]; final Block leftOver = result[ 1 ]; newB.used = used; removeFromFreed( availBlock ); if( !used ) addToFreed( newB ); array[ newB.start ] = newB; if( leftOver != null ) { array[ leftOver.start ] = leftOver; if( top > leftOver.start ) { addToFreed( leftOver ); } else { top = leftOver.start; } } return result; } public void debug() { Map.Entry entry; System.err.println( this.getClass().getName() + ":\n\nArray:" ); for( int i = 0; i < array.length; i++ ) { if( array[ i ] != null ) { System.err.println( String.valueOf( i ) + ": " + array[ i ]); } System.err.println( "\nFree sets:" ); for( Iterator iter = freed.entrySet().iterator(); iter.hasNext(); ) { entry = (Map.Entry) iter.next(); System.err.print( entry.getKey().toString() + ": [ " ); for( Iterator iter2 = ((Set) entry.getValue()).iterator(); iter2.hasNext(); ) { System.err.print( iter2.next().toString() + ", " ); } System.err.println( "]" ); } } } public static class Factory implements BlockAllocator.Factory { public BlockAllocator create( int size ) { return new ContiguousBlockAllocator( size ); } public BlockAllocator create( int size, int pos ) { return new ContiguousBlockAllocator( size, pos ); } } private static class Block implements BlockAllocator.Block { protected final int start; protected final int size; protected boolean used = false; // assume free; owner must say otherwise public Block( int start, int size ) { this.start = start; this.size = size; } public int getAddress() { return start; } public int getSize() { return size; } public boolean adjoins( Block b ) { return( ((start < b.start) && (start + size >= b.start)) || ((start > b.start) && (b.start + b.size >= start)) ); } public Block join( Block b ) { final int newStart; final int newSize; if( adjoins( b )) { newStart = Math.min( start, b.start ); newSize = Math.max( start + size, b.start + b.size ) - newStart; return new Block( newStart, newSize ); } else { return null; } } public Block[] split( int len ) { final Block[] result = new Block[ 2 ]; if( len < size ) { result[ 0 ] = new Block( start, len ); result[ 1 ] = new Block( start + len, size - len ); } else if( len == size ) { result[ 0 ] = this; } return result; } public String toString() { return( "Block( start = " + start + "; size = " + size + "; used = " + used + " )" ); } } }