-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf: Avoid Record memory allocs, BucketULongArray to avoid array.cop…
…yOf (#102) perf: Use array implementation of CompId2ArchetypeMap, avoid null boxing using getOrElse perf: Fully remove Record references (unnecessary memory allocs) perf: Use BucketedULongArray to avoid array.copyOf when in ArrayTypeMap, useful when creating many entities at once
- Loading branch information
Showing
26 changed files
with
389 additions
and
259 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/BucketedULongArray.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package com.mineinabyss.geary.datatypes | ||
|
||
private const val bucketSize: Int = 1024 | ||
|
||
class BucketedULongArray() { | ||
private val buckets = mutableListOf<LongArray>() | ||
var maxSupportedSize = 0 | ||
private set | ||
var size = 0 | ||
private set | ||
|
||
val lastIndex get() = size - 1 | ||
|
||
operator fun get(index: Int): ULong { | ||
val bucketIndex = index / bucketSize | ||
val bucket = buckets[bucketIndex] | ||
return bucket[index % bucketSize].toULong() | ||
} | ||
|
||
fun ensureSize(including: Int) { | ||
var maxSupportedSize = maxSupportedSize | ||
while (including >= maxSupportedSize) { | ||
buckets.add(LongArray(bucketSize)) | ||
maxSupportedSize += bucketSize | ||
} | ||
this.maxSupportedSize = maxSupportedSize | ||
} | ||
|
||
operator fun set(index: Int, value: ULong) { | ||
val bucketIndex = index / bucketSize | ||
ensureSize(index) | ||
val bucket = buckets[bucketIndex] | ||
if (index >= size) size = index + 1 | ||
bucket[index % bucketSize] = value.toLong() | ||
} | ||
|
||
fun add(value: ULong) { | ||
val index = size | ||
set(index, value) | ||
} | ||
|
||
fun getAll(): ULongArray { | ||
return ULongArray(size) { get(it) } | ||
} | ||
|
||
fun removeLastOrNull(): ULong? { | ||
if (size == 0) return null | ||
return get(lastIndex).also { | ||
size-- | ||
if (size % bucketSize == 0) buckets.removeLast() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 0 additions & 25 deletions
25
geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Record.kt
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 44 additions & 15 deletions
59
geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/ArrayTypeMap.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,62 @@ | ||
package com.mineinabyss.geary.datatypes.maps | ||
|
||
import com.mineinabyss.geary.datatypes.BucketedULongArray | ||
import com.mineinabyss.geary.datatypes.Entity | ||
import com.mineinabyss.geary.datatypes.Record | ||
import com.mineinabyss.geary.engine.archetypes.Archetype | ||
|
||
class ArrayTypeMap : TypeMap { | ||
private val map: ArrayList<Record?> = arrayListOf() | ||
|
||
open class ArrayTypeMap : TypeMap { | ||
@PublishedApi | ||
internal val archList = arrayListOf<Archetype>() | ||
|
||
// private val map: ArrayList<Record?> = arrayListOf() | ||
// private var archIndexes = IntArray(10) | ||
// private var rows = IntArray(10) | ||
@PublishedApi | ||
internal var archAndRow = BucketedULongArray() | ||
var size = 0 | ||
|
||
// We don't return nullable record to avoid boxing. | ||
// Accessing an entity that doesn't exist is indicative of a problem elsewhere and should be made obvious. | ||
override fun get(entity: Entity): Record = map[entity.id.toInt()] | ||
?: error("Tried to access components on an entity that no longer exists (${entity.id})") | ||
// override fun get(entity: Entity): Record { | ||
// val info = archAndRow[entity.id.toInt()] | ||
// return Record( | ||
// archList[(info shr 32).toInt()], | ||
// info.toInt() | ||
// ) | ||
// } | ||
open fun getArchAndRow(entity: Entity): ULong { | ||
return archAndRow[entity.id.toInt()] | ||
} | ||
|
||
override fun set(entity: Entity, record: Record) { | ||
override fun set(entity: Entity, archetype: Archetype, row: Int) { | ||
val id = entity.id.toInt() | ||
if (map.size == id) { | ||
map.add(record) | ||
return | ||
} | ||
if (contains(entity)) error("Tried setting the record of an entity that already exists.") | ||
while (map.size <= id) map.add(null) | ||
map[id] = record | ||
archAndRow[id] = (indexOrAdd(archetype).toULong() shl 32) or row.toULong() | ||
} | ||
|
||
fun indexOrAdd(archetype: Archetype): Int { | ||
if (archetype.indexInRecords != -1) return archetype.indexInRecords | ||
val index = archList.indexOf(archetype) | ||
archetype.indexInRecords = index | ||
return if (index == -1) { | ||
archList.add(archetype) | ||
archList.lastIndex | ||
} else index | ||
} | ||
|
||
override fun remove(entity: Entity) { | ||
map[entity.id.toInt()] = null | ||
val id = entity.id.toInt() | ||
archAndRow[id] = 0UL | ||
} | ||
|
||
override operator fun contains(entity: Entity): Boolean { | ||
val id = entity.id.toInt() | ||
return map.size > id && map[id] != null | ||
return id < archAndRow.size && archAndRow[id] != 0uL | ||
} | ||
|
||
|
||
inline fun <T> runOn(entity: Entity, run: (archetype: Archetype, row: Int) -> T): T { | ||
val info = getArchAndRow(entity) | ||
return run(archList[(info shr 32).toInt()], info.toInt()) | ||
} | ||
} |
72 changes: 44 additions & 28 deletions
72
geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/CompId2ArchetypeMap.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,66 @@ | ||
package com.mineinabyss.geary.datatypes.maps | ||
|
||
import com.mineinabyss.geary.datatypes.GearyComponentId | ||
import com.mineinabyss.geary.datatypes.IdList | ||
import com.mineinabyss.geary.engine.archetypes.Archetype | ||
|
||
/** | ||
* Inlined class that acts as a map of components to archetypes. Uses archetype ids for better performance. | ||
*/ | ||
expect class CompId2ArchetypeMap() { | ||
operator fun get(id: GearyComponentId): Archetype? | ||
operator fun set(id: GearyComponentId, archetype: Archetype) | ||
|
||
fun entries(): Set<Map.Entry<ULong, Archetype>> | ||
|
||
fun clear() | ||
|
||
fun remove(id: GearyComponentId) | ||
|
||
operator fun contains(id: GearyComponentId): Boolean | ||
|
||
fun getOrSet(id: GearyComponentId, put: () -> Archetype): Archetype | ||
|
||
val size: Int | ||
} | ||
|
||
class CompId2ArchetypeMapViaMutableMap { | ||
val inner: MutableMap<ULong, Archetype> = mutableMapOf() | ||
operator fun get(id: GearyComponentId): Archetype? = inner[id] | ||
class CompId2ArchetypeMap { | ||
// val inner = Long2ObjectArrayMap<Archetype>() | ||
val ids = IdList() | ||
val values = mutableListOf<Archetype>() | ||
// actual operator fun get(id: GearyComponentId): Archetype? = | ||
// values[entries.indexOf(id).also { if (it == -1) return null }] | ||
operator fun set(id: GearyComponentId, archetype: Archetype) { | ||
inner[id] = archetype | ||
val index = ids.indexOf(id) | ||
if (index == -1) { | ||
ids.add(id) | ||
values.add(archetype) | ||
} else { | ||
values[index] = archetype | ||
} | ||
} | ||
|
||
fun entries(): Set<Map.Entry<ULong, Archetype>> = inner.entries | ||
|
||
fun remove(id: GearyComponentId) { | ||
inner.remove(id) | ||
val index = ids.indexOf(id) | ||
if (index != -1) { | ||
ids.removeAt(index) | ||
values[index] = values[values.lastIndex] | ||
values.removeLast() | ||
} | ||
} | ||
|
||
fun clear() { | ||
inner.clear() | ||
ids.size = 0 | ||
values.clear() | ||
} | ||
|
||
inline fun forEach(action: (ULong, Archetype) -> Unit) { | ||
for(i in 0 until ids.size) { | ||
action(ids[i], values[i]) | ||
} | ||
} | ||
|
||
val size: Int get() = inner.size | ||
val size: Int get() = ids.size | ||
|
||
operator fun contains(id: GearyComponentId): Boolean = ids.indexOf(id) != -1 | ||
|
||
operator fun contains(id: GearyComponentId): Boolean = inner.containsKey(id) | ||
inline fun getOrElse(id: GearyComponentId, defaultValue: () -> Archetype): Archetype { | ||
val index = ids.indexOf(id) | ||
return if (index == -1) defaultValue() else values[index] | ||
} | ||
|
||
fun getOrSet(id: GearyComponentId, put: () -> Archetype): Archetype { | ||
return inner[id] ?: put().also { inner[id] = it } | ||
inline fun getOrSet(id: GearyComponentId, put: () -> Archetype): Archetype { | ||
val index = ids.indexOf(id) | ||
if (index == -1) { | ||
val arc = put() | ||
ids.add(id) | ||
values.add(arc) | ||
return arc | ||
} | ||
return values[index] | ||
} | ||
} |
17 changes: 11 additions & 6 deletions
17
geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/SynchronizedTypeMap.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,20 @@ | ||
package com.mineinabyss.geary.datatypes.maps | ||
|
||
import com.mineinabyss.geary.datatypes.Entity | ||
import com.mineinabyss.geary.datatypes.Record | ||
import com.mineinabyss.geary.engine.archetypes.Archetype | ||
import kotlinx.atomicfu.locks.SynchronizedObject | ||
import kotlinx.atomicfu.locks.synchronized | ||
|
||
class SynchronizedTypeMap(private val map: TypeMap) : TypeMap { | ||
class SynchronizedArrayTypeMap() : ArrayTypeMap() { | ||
private val lock = SynchronizedObject() | ||
|
||
override fun get(entity: Entity): Record = synchronized(lock) { map[entity] } | ||
override fun set(entity: Entity, record: Record) = synchronized(lock) { map[entity] = record } | ||
override fun remove(entity: Entity) = synchronized(lock) { map.remove(entity) } | ||
override fun contains(entity: Entity): Boolean = synchronized(lock) { map.contains(entity) } | ||
override fun getArchAndRow(entity: Entity): ULong { | ||
return synchronized(lock) { super.getArchAndRow(entity) } | ||
} | ||
override fun set(entity: Entity, archetype: Archetype, row: Int) { | ||
synchronized(lock) { super.set(entity, archetype, row) } | ||
} | ||
|
||
override fun remove(entity: Entity) = synchronized(lock) { super.remove(entity) } | ||
override fun contains(entity: Entity): Boolean = synchronized(lock) { super.contains(entity) } | ||
} |
Oops, something went wrong.