/*
 * Copyright (c) 2002-2011 LWJGL Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'LWJGL' nor the names of
 *   its contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.lwjgl.util.mapped;

import org.lwjgl.LWJGLUtil;
import org.lwjgl.MemoryUtil;
import org.lwjgl.PointerBuffer;

import java.nio.*;

/**
 * This class provides utility methods for allocating cache-line-aligned
 * NIO buffers. The CPU cache line size is detected using a micro-benchmark
 * that exploits the performation degredation that occurs when different
 * threads write to different locations of the same cache line. The detection
 * should be reasonably robust on both the server and client VM, but there
 * are a few system properties that can be used to tune it.
 *
 * @author Spasi
 */
public final class CacheUtil {

	private static final int CACHE_LINE_SIZE;

	static {
		final Integer size = LWJGLUtil.getPrivilegedInteger("org.lwjgl.util.mapped.CacheLineSize"); // forces a specific cache line size

		if ( size != null ) {
			if ( size < 1 )
				throw new IllegalStateException("Invalid CacheLineSize specified: " + size);
			CACHE_LINE_SIZE = size;
		} else if ( Runtime.getRuntime().availableProcessors() == 1 ) { // We cannot use false sharing to detect it
			/*
			    Spasi:

			    I have implemented a single-threaded benchmark for this, but it requires
			    lots of memory allocations and could not tune it for both the client and
			    server VM. It's not a big deal anyway, 64 bytes should be ok for any
			    single-core CPU.
			*/
			if ( LWJGLUtil.DEBUG )
				LWJGLUtil.log("Cannot detect cache line size on single-core CPUs, assuming 64 bytes.");
			CACHE_LINE_SIZE = 64;
		} else
			CACHE_LINE_SIZE = CacheLineSize.getCacheLineSize();
	}

	private CacheUtil() {
	}

	/**
	 * Returns the CPU cache line size, in number of bytes.
	 *
	 * @return the cache line size
	 */
	public static int getCacheLineSize() {
		return CACHE_LINE_SIZE;
	}

	/**
	 * Construct a direct, native-ordered and cache-line-aligned bytebuffer with the specified size.
	 *
	 * @param size The size, in bytes
	 *
	 * @return a ByteBuffer
	 */
	public static ByteBuffer createByteBuffer(int size) {
		ByteBuffer buffer = ByteBuffer.allocateDirect(size + CACHE_LINE_SIZE);

		// Align to cache line.
		if ( MemoryUtil.getAddress(buffer) % CACHE_LINE_SIZE != 0 ) {
			// Round up to cache line boundary
			buffer.position(CACHE_LINE_SIZE - (int)(MemoryUtil.getAddress(buffer) & (CACHE_LINE_SIZE - 1)));
		}

		buffer.limit(buffer.position() + size);
		return buffer.slice().order(ByteOrder.nativeOrder());
	}

	/**
	 * Construct a direct, native-ordered and cache-line-aligned shortbuffer with the specified number
	 * of elements.
	 *
	 * @param size The size, in shorts
	 *
	 * @return a ShortBuffer
	 */
	public static ShortBuffer createShortBuffer(int size) {
		return createByteBuffer(size << 1).asShortBuffer();
	}

	/**
	 * Construct a direct, native-ordered and cache-line-aligned charbuffer with the specified number
	 * of elements.
	 *
	 * @param size The size, in chars
	 *
	 * @return an CharBuffer
	 */
	public static CharBuffer createCharBuffer(int size) {
		return createByteBuffer(size << 1).asCharBuffer();
	}

	/**
	 * Construct a direct, native-ordered and cache-line-aligned intbuffer with the specified number
	 * of elements.
	 *
	 * @param size The size, in ints
	 *
	 * @return an IntBuffer
	 */
	public static IntBuffer createIntBuffer(int size) {
		return createByteBuffer(size << 2).asIntBuffer();
	}

	/**
	 * Construct a direct, native-ordered and cache-line-aligned longbuffer with the specified number
	 * of elements.
	 *
	 * @param size The size, in longs
	 *
	 * @return an LongBuffer
	 */
	public static LongBuffer createLongBuffer(int size) {
		return createByteBuffer(size << 3).asLongBuffer();
	}

	/**
	 * Construct a direct, native-ordered and cache-line-aligned floatbuffer with the specified number
	 * of elements.
	 *
	 * @param size The size, in floats
	 *
	 * @return a FloatBuffer
	 */
	public static FloatBuffer createFloatBuffer(int size) {
		return createByteBuffer(size << 2).asFloatBuffer();
	}

	/**
	 * Construct a direct, native-ordered and cache-line-aligned doublebuffer with the specified number
	 * of elements.
	 *
	 * @param size The size, in floats
	 *
	 * @return a FloatBuffer
	 */
	public static DoubleBuffer createDoubleBuffer(int size) {
		return createByteBuffer(size << 3).asDoubleBuffer();
	}

	/**
	 * Construct a cache-line-aligned PointerBuffer with the specified number
	 * of elements.
	 *
	 * @param size The size, in memory addresses
	 *
	 * @return a PointerBuffer
	 */
	public static PointerBuffer createPointerBuffer(int size) {
		return new PointerBuffer(createByteBuffer(size * PointerBuffer.getPointerSize()));
	}

}