Skip to content

Commit

Permalink
Turn NodeSet into a value class.
Browse files Browse the repository at this point in the history
This change should improve performance to some degree due to less objects being created at runtime.
  • Loading branch information
Flolle committed May 5, 2021
1 parent 1374083 commit d5f2ab0
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 57 deletions.
28 changes: 15 additions & 13 deletions src/terminalFlood/algo/astar/AStar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -233,19 +233,20 @@ object AStar {
// Only allow a move if the previous move added new border nodes of the next move's color.
var allowedMoves = ColorSet()
val previousMove = gameState.lastMove
val nextMoveNeighbors = NodeSet(gameState.gameBoard.amountOfNodes)
val amountOfBits = gameState.gameBoard.amountOfNodes
val nextMoveNeighbors = NodeSet(amountOfBits)
gameState.sensibleMoves.forEachColor { color ->
nextMoveNeighbors.setToNeighborsWithColor(gameState, color)
var i = nextMoveNeighbors.nextSetBit(0)
var i = nextMoveNeighbors.nextSetBit(0, amountOfBits)
outerLoop@ while (i >= 0) {
val borderingNodes = gameState.gameBoard.getNodeWithIndex(i).borderingNodes
var j = borderingNodes.nextSetBit(0)
var j = borderingNodes.nextSetBit(0, amountOfBits)
while (j >= 0) {
if (gameState.gameBoard.getNodeWithIndex(j).color != previousMove && gameState.filled[j]) {
i = nextMoveNeighbors.nextSetBit(i + 1)
i = nextMoveNeighbors.nextSetBit(i + 1, amountOfBits)
continue@outerLoop
}
j = borderingNodes.nextSetBit(j + 1)
j = borderingNodes.nextSetBit(j + 1, amountOfBits)
}

allowedMoves += color
Expand Down Expand Up @@ -298,17 +299,18 @@ object AStar {

// Did the previous move add any new "nextMove" border nodes?
val previousMove = gameState.lastMove
val amountOfBits = gameState.gameBoard.amountOfNodes
var isNewBorderNodes = false
var i = borderNodesByColor.nextSetBit(0)
var i = borderNodesByColor.nextSetBit(0, amountOfBits)
outerLoop@ while (i >= 0) {
val borderingNodes = gameState.gameBoard.getNodeWithIndex(i).borderingNodes
var j = borderingNodes.nextSetBit(0)
var j = borderingNodes.nextSetBit(0, amountOfBits)
while (j >= 0) {
if (gameState.gameBoard.getNodeWithIndex(j).color != previousMove && gameState.filled[j]) {
i = borderNodesByColor.nextSetBit(i + 1)
i = borderNodesByColor.nextSetBit(i + 1, amountOfBits)
continue@outerLoop
}
j = borderingNodes.nextSetBit(j + 1)
j = borderingNodes.nextSetBit(j + 1, amountOfBits)
}

isNewBorderNodes = true
Expand All @@ -320,18 +322,18 @@ object AStar {
return false

// Should nextMove have been played before previousMove?
i = borderNodesByColor.nextSetBit(0)
i = borderNodesByColor.nextSetBit(0, amountOfBits)
while (i >= 0) {
val borderingNodes = gameState.gameBoard.getNodeWithIndex(i).borderingNodes
var j = borderingNodes.nextSetBit(0)
var j = borderingNodes.nextSetBit(0, amountOfBits)
while (j >= 0) {
if (gameState.gameBoard.getNodeWithIndex(j).color == previousMove && !gameState.filled[j]) {
return false
}
j = borderingNodes.nextSetBit(j + 1)
j = borderingNodes.nextSetBit(j + 1, amountOfBits)
}

i = borderNodesByColor.nextSetBit(i + 1)
i = borderNodesByColor.nextSetBit(i + 1, amountOfBits)
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/terminalFlood/game/Game.kt
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,15 @@ class Game(

if (playedMoves != other.playedMoves) return false
if (gameBoard != other.gameBoard) return false
if (filled != other.filled) return false
if (!filled.contentEquals(other.filled)) return false

return true
}

override fun hashCode(): Int {
var result = gameBoard.hashCode()
result = 31 * result + playedMoves.hashCode()
result = 31 * result + filled.hashCode()
result = 31 * result + filled.contentHashCode()
return result
}
}
2 changes: 1 addition & 1 deletion src/terminalFlood/game/GameBoard.kt
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ private data class BoardNodeImpl(
override val color: Color,
override val occupiedFields: List<Point>
) : BoardNode {
override lateinit var borderingNodes: NodeSet
override var borderingNodes: NodeSet = NodeSet.EMPTY

override var id: Int = -1

Expand Down
4 changes: 2 additions & 2 deletions src/terminalFlood/game/GameExternalMoveList.kt
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,15 @@ class GameExternalMoveList(

if (moveEntryIndex != other.moveEntryIndex) return false
if (gameBoard != other.gameBoard) return false
if (filled != other.filled) return false
if (!filled.contentEquals(other.filled)) return false

return true
}

override fun hashCode(): Int {
var result = gameBoard.hashCode()
result = 31 * result + moveEntryIndex
result = 31 * result + filled.hashCode()
result = 31 * result + filled.contentHashCode()
return result
}
}
74 changes: 35 additions & 39 deletions src/terminalFlood/game/NodeSet.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package terminalFlood.game

import java.util.*

/**
* This is a bitmap implementation specific to [BoardNode]s.
*
Expand All @@ -10,13 +12,18 @@ package terminalFlood.game
*
* The bit indexes and [BoardNode]s are linked through [BoardNode.id].
*
* Note that [contentEquals] and [contentHashCode] should be used because the normal `equals` and `hashCode` methods
* will use the internal LongArray and not its contents.
*
* @see BoardNode
*/
class NodeSet private constructor(
val words: LongArray,
private val amountOfBits: Int
@JvmInline
value class NodeSet(
val words: LongArray
) {
companion object {
val EMPTY: NodeSet = NodeSet(LongArray(0))

private fun bitsToWords(amountOfBits: Int): Int = ((amountOfBits - 1) shr 6) + 1

/**
Expand All @@ -34,9 +41,7 @@ class NodeSet private constructor(
}
}

constructor(amountOfBits: Int) : this(LongArray(bitsToWords(amountOfBits)), amountOfBits)

private val amountOfWords: Int = words.size
constructor(amountOfBits: Int) : this(LongArray(bitsToWords(amountOfBits)))

/**
* Returns true if this bitset contains no bits set to true.
Expand Down Expand Up @@ -100,16 +105,14 @@ class NodeSet private constructor(
* Will set all bits in this bitset to false.
*/
fun clear() {
var i = amountOfWords
while (--i >= 0)
words[i] = 0
Arrays.fill(words, 0, words.size, 0)
}

/**
* Will inverse this bitset.
*/
fun flipAll() {
var i = amountOfWords
var i = words.size
while (--i >= 0)
words[i] = words[i].inv()
}
Expand All @@ -122,16 +125,17 @@ class NodeSet private constructor(
// Assumes both NodeSets have the same internal array size.
@Suppress("NOTHING_TO_INLINE")
inline fun setToBorderingNodesOf(gameBoard: GameBoard, nodes: NodeSet) {
var i = nodes.nextSetBit(0)
val amountOfBits = gameBoard.amountOfNodes
var i = nodes.nextSetBit(0, amountOfBits)
if (i == -1) {
clear()
return
}
setTo(gameBoard.getNodeWithIndex(i).borderingNodes)
i = nodes.nextSetBit(i + 1)
i = nodes.nextSetBit(i + 1, amountOfBits)
while (i > 0) {
or(gameBoard.getNodeWithIndex(i).borderingNodes)
i = nodes.nextSetBit(i + 1)
i = nodes.nextSetBit(i + 1, amountOfBits)
}
}

Expand All @@ -150,7 +154,7 @@ class NodeSet private constructor(
*/
// Assumes both NodeSets have the same internal array size.
fun setToIntersection(bitset1: NodeSet, bitset2: NodeSet) {
var i = amountOfWords
var i = words.size
while (--i >= 0)
words[i] = bitset1.words[i] and bitset2.words[i]
}
Expand All @@ -160,17 +164,15 @@ class NodeSet private constructor(
*/
// Assumes both NodeSets have the same internal array size.
fun setTo(nodes: NodeSet) {
var i = amountOfWords
while (--i >= 0)
words[i] = nodes.words[i]
System.arraycopy(nodes.words, 0, words, 0, words.size)
}

/**
* Performs a logical OR of this bitset with the given bitset.
*/
// Assumes both NodeSets have the same internal array size.
fun or(nodes: NodeSet) {
var i = amountOfWords
var i = words.size
while (--i >= 0)
words[i] = words[i] or nodes.words[i]
}
Expand All @@ -180,7 +182,7 @@ class NodeSet private constructor(
*/
// Assumes both NodeSets have the same internal array size.
fun xor(nodes: NodeSet) {
var i = amountOfWords
var i = words.size
while (--i >= 0)
words[i] = words[i] xor nodes.words[i]
}
Expand All @@ -190,7 +192,7 @@ class NodeSet private constructor(
*/
// Assumes both NodeSets have the same internal array size.
fun and(nodes: NodeSet) {
var i = amountOfWords
var i = words.size
while (--i >= 0)
words[i] = words[i] and nodes.words[i]
}
Expand All @@ -200,7 +202,7 @@ class NodeSet private constructor(
*/
// Assumes both NodeSets have the same internal array size.
fun andNot(nodes: NodeSet) {
var i = amountOfWords
var i = words.size
while (--i >= 0)
words[i] = words[i] and nodes.words[i].inv()
}
Expand All @@ -210,18 +212,20 @@ class NodeSet private constructor(
*/
// Assumes both NodeSets have the same internal array size.
fun intersects(nodes: NodeSet): Boolean {
var i = amountOfWords
while (--i >= 0)
var i = words.size
while (--i >= 0) {
if (words[i] and nodes.words[i] != 0L)
return true
}

return false
}

/**
* Returns the index of the first bit that is set to true that occurs on or after the specified starting index.
* Returns the index of the first bit that is set to true that occurs on or after the specified starting index
* within the given total amount of bits.
*/
fun nextSetBit(index: Int): Int {
fun nextSetBit(index: Int, amountOfBits: Int): Int {
if (index >= amountOfBits)
return -1

Expand All @@ -231,7 +235,7 @@ class NodeSet private constructor(
if (word != 0L)
return index + word.countTrailingZeroBits()

while (++i < amountOfWords) {
while (++i < words.size) {
word = words[i]
if (word != 0L)
return (i shl 6) + word.countTrailingZeroBits()
Expand Down Expand Up @@ -267,20 +271,12 @@ class NodeSet private constructor(
/**
* Creates a copy of this bitset.
*/
fun copy(): NodeSet = NodeSet(words.copyOf(), amountOfBits)

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is NodeSet) return false
fun copy(): NodeSet = NodeSet(words.copyOf())

if (amountOfBits != other.amountOfBits) return false
if (!words.contentEquals(other.words)) return false

return true
}
fun contentEquals(other: NodeSet): Boolean = words.contentEquals(other.words)

override fun hashCode(): Int {
val h = wordsToHash(words, 0, amountOfWords)
fun contentHashCode(): Int {
val h = wordsToHash(words, 0, words.size)
// fold leftmost bits into right and add a constant to prevent
// empty sets from returning 0, which is too common.
return (h shr 32 xor h).toInt() + -0x6789edcc
Expand All @@ -290,7 +286,7 @@ class NodeSet private constructor(
val str = StringBuilder()

str.append("[")
repeat(amountOfBits) {
repeat(words.size * 64) {
if (get(it))
str.append("1")
else
Expand Down

0 comments on commit d5f2ab0

Please sign in to comment.