mirror of
https://github.com/curioustorvald/Terrarum-sans-bitmap.git
synced 2026-06-06 05:58:30 +09:00
fixing memory leak caused by my sloppy-ass coding
This commit is contained in:
Binary file not shown.
BIN
demo.PNG
BIN
demo.PNG
Binary file not shown.
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 145 KiB |
@@ -34,10 +34,12 @@ import net.torvald.terrarumsansbitmap.GlyphProps
|
|||||||
import java.io.BufferedOutputStream
|
import java.io.BufferedOutputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
import java.util.*
|
||||||
import java.util.zip.CRC32
|
import java.util.zip.CRC32
|
||||||
import java.util.zip.GZIPInputStream
|
import java.util.zip.GZIPInputStream
|
||||||
import kotlin.NullPointerException
|
import kotlin.NullPointerException
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.math.sign
|
||||||
|
|
||||||
typealias CodepointSequence = ArrayList<CodePoint>
|
typealias CodepointSequence = ArrayList<CodePoint>
|
||||||
internal typealias CodePoint = Int
|
internal typealias CodePoint = Int
|
||||||
@@ -67,7 +69,8 @@ internal typealias Hash = Long
|
|||||||
* Glyphs are drawn lazily (calculated on-the-fly, rather than load up all), which is inevitable as we just can't load
|
* Glyphs are drawn lazily (calculated on-the-fly, rather than load up all), which is inevitable as we just can't load
|
||||||
* up 40k+ characters on the machine, which will certainly make loading time painfully long.
|
* up 40k+ characters on the machine, which will certainly make loading time painfully long.
|
||||||
*
|
*
|
||||||
* Color Codes have following Unicode mapping: U+10RGBA, A must be non-zero to be visible. U+100000 reverts any colour code effects.
|
* Color Codes have following Unicode mapping: U+10RGBA, A must be non-zero to be visible. U+100000 reverts any colour
|
||||||
|
* code effects.
|
||||||
*
|
*
|
||||||
* ## Control Characters
|
* ## Control Characters
|
||||||
*
|
*
|
||||||
@@ -79,16 +82,19 @@ internal typealias Hash = Long
|
|||||||
*
|
*
|
||||||
* ## Auto Shift Down
|
* ## Auto Shift Down
|
||||||
*
|
*
|
||||||
* Certain characters (e.g. Combining Diacritical Marks) will automatically shift down to accomodate lowercase letters. Shiftdown only occurs when non-diacritic character before the mark is lowercase, and the mark itself would stack up. Stack-up or down is defined using Tag system.
|
* Certain characters (e.g. Combining Diacritical Marks) will automatically shift down to accomodate lowercase letters.
|
||||||
|
* Shiftdown only occurs when non-diacritic character before the mark is lowercase, and the mark itself would stack up.
|
||||||
|
* Stack-up or down is defined using Tag system.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @param noShadow Self-explanatory
|
* @param noShadow Self-explanatory
|
||||||
* @param flipY If you have Y-down coord system implemented on your GDX (e.g. legacy codebase), set this to ```true``` so that the shadow won't be upside-down. For glyph getting upside-down, set ```TextureRegionPack.globalFlipY = true```.
|
* @param flipY If you have Y-down coord system implemented on your GDX (e.g. legacy codebase), set this to ```true```
|
||||||
|
* so that the shadow won't be upside-down. For glyph getting upside-down, set ```TextureRegionPack.globalFlipY = true```.
|
||||||
*
|
*
|
||||||
* Created by minjaesong on 2017-06-15.
|
* Created by minjaesong on 2017-06-15.
|
||||||
*/
|
*/
|
||||||
class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Boolean = false, val minFilter: Texture.TextureFilter = Texture.TextureFilter.Nearest, val magFilter: Texture.TextureFilter = Texture.TextureFilter.Nearest, var errorOnUnknownChar: Boolean = false, val textCacheSize: Int = 64, val debug: Boolean = false) : BitmapFont() {
|
class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Boolean = false, val minFilter: Texture.TextureFilter = Texture.TextureFilter.Nearest, val magFilter: Texture.TextureFilter = Texture.TextureFilter.Nearest, var errorOnUnknownChar: Boolean = false, val textCacheSize: Int = 256, val debug: Boolean = false) : BitmapFont() {
|
||||||
|
|
||||||
// Hangul Implementation Specific //
|
// Hangul Implementation Specific //
|
||||||
|
|
||||||
@@ -288,11 +294,19 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo
|
|||||||
|
|
||||||
|
|
||||||
// TODO (val posXbuffer: IntArray, val posYbuffer: IntArray) -> (val linotype: Pixmap)
|
// TODO (val posXbuffer: IntArray, val posYbuffer: IntArray) -> (val linotype: Pixmap)
|
||||||
private data class ShittyGlyphLayout(val textBuffer: CodepointSequence, val linotype: Pixmap, val width: Int)
|
private data class ShittyGlyphLayout(val textBuffer: CodepointSequence, val linotype: Texture, val width: Int)
|
||||||
|
|
||||||
//private val textCache = HashMap<CharSequence, ShittyGlyphLayout>()
|
//private val textCache = HashMap<CharSequence, ShittyGlyphLayout>()
|
||||||
|
|
||||||
private data class TextCacheObj(var age: Int, val hash: Long, val glyphLayout: ShittyGlyphLayout?)
|
private data class TextCacheObj(var age: Int, val hash: Long, val glyphLayout: ShittyGlyphLayout?): Comparable<TextCacheObj> {
|
||||||
|
fun dispose() {
|
||||||
|
glyphLayout?.linotype?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun compareTo(other: TextCacheObj): Int {
|
||||||
|
return (this.hash - other.hash).sign
|
||||||
|
}
|
||||||
|
}
|
||||||
private var textCacheCap = 0
|
private var textCacheCap = 0
|
||||||
|
|
||||||
private val textCache = Array(textCacheSize) { TextCacheObj(-1, -1L, null) }
|
private val textCache = Array(textCacheSize) { TextCacheObj(-1, -1L, null) }
|
||||||
@@ -300,53 +314,50 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo
|
|||||||
/**
|
/**
|
||||||
* Insertion sorts the last element fo the textCache
|
* Insertion sorts the last element fo the textCache
|
||||||
*/
|
*/
|
||||||
private fun addToCache(text: CodepointSequence, linotype: Pixmap, width: Int) {
|
private fun addToCache(text: CodepointSequence, linotype: Texture, width: Int) {
|
||||||
// make room first
|
// Caching rules:
|
||||||
if (textCacheCap == textCacheSize - 1) {
|
// 1. always accept new element.
|
||||||
var c = 0
|
// 2. often-called element have higher chance of survival (see: getCache(long))
|
||||||
var mark = -1
|
|
||||||
while (c < textCacheSize - 1) {
|
|
||||||
if (textCache[c].age == 0 && mark == -1) // if unmarked and age == 0, mark it
|
|
||||||
mark = c
|
|
||||||
|
|
||||||
if (mark >= 0) { // if marked then ...
|
|
||||||
// shift left everyting by 1
|
if (textCacheCap < textCacheSize) {
|
||||||
textCache[c] = textCache[c + 1]
|
textCache[textCacheCap] = TextCacheObj(0, text.getHash(), ShittyGlyphLayout(text, linotype, width))
|
||||||
|
textCacheCap += 1
|
||||||
|
|
||||||
|
// make everybody age
|
||||||
|
textCache.forEach {
|
||||||
|
it.age += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// search for an oldest element
|
||||||
|
var oldestElemIndex = 0
|
||||||
|
var ageOfOldest = textCacheCap
|
||||||
|
textCache.forEachIndexed { index, it ->
|
||||||
|
// make everybody age
|
||||||
|
textCache[index].age += 1
|
||||||
|
|
||||||
|
// mark oldest
|
||||||
|
if (it.age > ageOfOldest) {
|
||||||
|
oldestElemIndex = index
|
||||||
}
|
}
|
||||||
|
|
||||||
c += 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// dispose of the oldest one before overwriting
|
||||||
|
textCache[oldestElemIndex].dispose()
|
||||||
|
|
||||||
|
// overwrite oldest one
|
||||||
|
textCache[oldestElemIndex] = TextCacheObj(0, text.getHash(), ShittyGlyphLayout(text, linotype, width))
|
||||||
}
|
}
|
||||||
|
|
||||||
// count down all the elements' age by 1
|
// sort the list
|
||||||
textCache.forEach { it.age -= 1 }
|
textCache.sortBy { it.hash }
|
||||||
|
|
||||||
|
|
||||||
// put new element at the end
|
|
||||||
textCache[textCacheCap] = TextCacheObj(textCacheCap, text.getHash(), ShittyGlyphLayout(text, linotype, width))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// insertion sort last elem (sorted by hash)
|
|
||||||
if (textCacheCap >= 1) { // when there's two or more elem...
|
|
||||||
var j = textCacheCap - 1
|
|
||||||
val x = textCache[textCacheCap]
|
|
||||||
while (j >= 0 && textCache[j].hash > x.hash) {
|
|
||||||
textCache[j + 1] = textCache[j]
|
|
||||||
j -= 1
|
|
||||||
}
|
|
||||||
textCache[j + 1] = x
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (textCacheCap < textCacheSize - 1) {
|
|
||||||
textCacheCap++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCache(hash: Long): TextCacheObj {
|
private fun getCache(hash: Long): TextCacheObj? {
|
||||||
var low = 0
|
var low = 0
|
||||||
var high = textCacheCap
|
var high = textCacheCap - 1
|
||||||
var key = -1
|
var key = -1
|
||||||
|
|
||||||
|
|
||||||
@@ -366,34 +377,14 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (key < 0)
|
if (key < 0)
|
||||||
throw NullPointerException("No element found")
|
return null
|
||||||
|
|
||||||
|
// increment age count (see: addToCache(CodepointSequence, Pixmap, Int))
|
||||||
|
textCache[key].age += 1
|
||||||
|
|
||||||
return textCache[key]
|
return textCache[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cacheContains(hash: Long): Boolean {
|
|
||||||
var low = 0
|
|
||||||
var high = textCacheCap
|
|
||||||
var key = -1
|
|
||||||
|
|
||||||
|
|
||||||
while (low <= high) {
|
|
||||||
val mid = (low + high).ushr(1) // safe from overflows
|
|
||||||
|
|
||||||
val midVal = textCache[mid]
|
|
||||||
|
|
||||||
if (hash > midVal.hash)
|
|
||||||
low = mid + 1
|
|
||||||
else if (hash < midVal.hash)
|
|
||||||
high = mid - 1
|
|
||||||
else {
|
|
||||||
key = mid
|
|
||||||
break // the condition is not enough to break the loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (key >= 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getColour(codePoint: Int): Int { // input: 0x10F_RGB, out: RGBA8888
|
private fun getColour(codePoint: Int): Int { // input: 0x10F_RGB, out: RGBA8888
|
||||||
if (colourBuffer.containsKey(codePoint))
|
if (colourBuffer.containsKey(codePoint))
|
||||||
@@ -660,32 +651,24 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo
|
|||||||
|
|
||||||
private var flagFirstRun = true
|
private var flagFirstRun = true
|
||||||
private var textBuffer = CodepointSequence(256)
|
private var textBuffer = CodepointSequence(256)
|
||||||
//private var oldCharSequence = ""
|
|
||||||
//private var posXbuffer = intArrayOf() // absolute posX of glyphs from print-origin
|
private lateinit var tempLinotype: Texture
|
||||||
//private var posYbuffer = intArrayOf() // absolute posY of glyphs from print-origin
|
|
||||||
private var linotype: Pixmap? = null
|
|
||||||
|
|
||||||
private lateinit var originalColour: Color
|
private lateinit var originalColour: Color
|
||||||
|
|
||||||
private var nullProp = GlyphProps(15, 0)
|
private var nullProp = GlyphProps(15, 0)
|
||||||
|
|
||||||
private var pixmapTextureHolder: Texture? = null
|
|
||||||
//private var pixmapHolder: Pixmap? = null
|
|
||||||
|
|
||||||
private val pixmapOffsetY = 10
|
private val pixmapOffsetY = 10
|
||||||
|
|
||||||
override fun draw(batch: Batch, charSeq: CharSequence, x: Float, y: Float): GlyphLayout? {
|
override fun draw(batch: Batch, charSeq: CharSequence, x: Float, y: Float): GlyphLayout? {
|
||||||
if (debug)
|
if (debug)
|
||||||
println("[TerrarumSansBitmap] max age: $textCacheCap")
|
println("[TerrarumSansBitmap] max age: $textCacheCap")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun Int.flipY() = this * if (flipY) 1 else -1
|
fun Int.flipY() = this * if (flipY) 1 else -1
|
||||||
|
|
||||||
|
|
||||||
// always draw at integer position; this is bitmap font after all
|
// always draw at integer position; this is bitmap font after all
|
||||||
val x = Math.round(x).toInt()//.toFloat()
|
val x = Math.round(x)
|
||||||
val y = Math.round(y).toInt()//.toFloat()
|
val y = Math.round(y)
|
||||||
|
|
||||||
originalColour = batch.color.cpy()
|
originalColour = batch.color.cpy()
|
||||||
val mainColObj = originalColour
|
val mainColObj = originalColour
|
||||||
@@ -695,12 +678,13 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo
|
|||||||
|
|
||||||
if (charSeq.isNotBlank()) {
|
if (charSeq.isNotBlank()) {
|
||||||
|
|
||||||
if (!cacheContains(charSeqHash) || flagFirstRun) {
|
val cacheObj = getCache(charSeqHash)
|
||||||
|
|
||||||
|
if (cacheObj == null || flagFirstRun) {
|
||||||
textBuffer = charSeq.toCodePoints()
|
textBuffer = charSeq.toCodePoints()
|
||||||
|
|
||||||
val (posXbuffer, posYbuffer) = buildWidthAndPosBuffers(textBuffer)
|
val (posXbuffer, posYbuffer) = buildWidthAndPosBuffers(textBuffer)
|
||||||
|
|
||||||
linotype = null // use new linotype
|
|
||||||
flagFirstRun = false
|
flagFirstRun = false
|
||||||
|
|
||||||
//println("text not in buffer: $charSeq")
|
//println("text not in buffer: $charSeq")
|
||||||
@@ -712,18 +696,7 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo
|
|||||||
|
|
||||||
resetHash(charSeq, x.toFloat(), y.toFloat())
|
resetHash(charSeq, x.toFloat(), y.toFloat())
|
||||||
|
|
||||||
//pixmapTextureHolder?.dispose() /* you CAN'T do this however */
|
val linotypePixmap = Pixmap(posXbuffer.last(), H + (pixmapOffsetY * 2), Pixmap.Format.RGBA8888)
|
||||||
|
|
||||||
linotype = Pixmap(posXbuffer.last(), H + (pixmapOffsetY * 2), Pixmap.Format.RGBA8888)
|
|
||||||
|
|
||||||
// TEST: does the new instance of pixmap is all zero?
|
|
||||||
/*repeat(pixmapHolder?.pixels?.capacity() ?: 0) {
|
|
||||||
if (pixmapHolder?.pixels?.get() != 0.toByte()) {
|
|
||||||
throw InternalError("pixmap is not all zero, wtf?!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pixmapHolder?.pixels?.rewind()*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var index = 0
|
var index = 0
|
||||||
@@ -771,10 +744,10 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo
|
|||||||
val jungTex = hangulSheet.get(indexJung, jungRow)
|
val jungTex = hangulSheet.get(indexJung, jungRow)
|
||||||
val jongTex = hangulSheet.get(indexJong, jongRow)
|
val jongTex = hangulSheet.get(indexJong, jongRow)
|
||||||
|
|
||||||
linotype?.setColor(mainCol)
|
linotypePixmap.setColor(mainCol)
|
||||||
linotype?.drawPixmap(choTex, posXbuffer[index], pixmapOffsetY, mainCol)
|
linotypePixmap.drawPixmap(choTex, posXbuffer[index], pixmapOffsetY, mainCol)
|
||||||
linotype?.drawPixmap(jungTex, posXbuffer[index], pixmapOffsetY, mainCol)
|
linotypePixmap.drawPixmap(jungTex, posXbuffer[index], pixmapOffsetY, mainCol)
|
||||||
linotype?.drawPixmap(jongTex, posXbuffer[index], pixmapOffsetY, mainCol)
|
linotypePixmap.drawPixmap(jongTex, posXbuffer[index], pixmapOffsetY, mainCol)
|
||||||
|
|
||||||
//batch.color = mainCol
|
//batch.color = mainCol
|
||||||
//batch.draw(choTex, x + posXbuffer[index].toFloat(), y)
|
//batch.draw(choTex, x + posXbuffer[index].toFloat(), y)
|
||||||
@@ -797,7 +770,7 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo
|
|||||||
val posX = posXbuffer[index]
|
val posX = posXbuffer[index]
|
||||||
val texture = sheets[sheetID].get(sheetX, sheetY)
|
val texture = sheets[sheetID].get(sheetX, sheetY)
|
||||||
|
|
||||||
linotype?.drawPixmap(texture, posX, posY + pixmapOffsetY, mainCol)
|
linotypePixmap.drawPixmap(texture, posX, posY + pixmapOffsetY, mainCol)
|
||||||
|
|
||||||
//batch.color = mainCol
|
//batch.color = mainCol
|
||||||
//batch.draw(texture, posX, posY)
|
//batch.draw(texture, posX, posY)
|
||||||
@@ -813,34 +786,34 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
makeShadow(linotype)
|
makeShadow(linotypePixmap)
|
||||||
|
|
||||||
|
tempLinotype = Texture(linotypePixmap)
|
||||||
|
tempLinotype.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
|
||||||
|
|
||||||
// put things into cache
|
// put things into cache
|
||||||
//textCache[charSeq] = ShittyGlyphLayout(textBuffer, linotype!!)
|
//textCache[charSeq] = ShittyGlyphLayout(textBuffer, linotype!!)
|
||||||
addToCache(textBuffer, linotype!!, posXbuffer.last())
|
addToCache(textBuffer, tempLinotype, posXbuffer.last())
|
||||||
|
linotypePixmap.dispose()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
val bufferObj = getCache(charSeqHash)
|
textBuffer = cacheObj.glyphLayout!!.textBuffer
|
||||||
|
tempLinotype = cacheObj.glyphLayout!!.linotype
|
||||||
textBuffer = bufferObj.glyphLayout!!.textBuffer
|
|
||||||
linotype = bufferObj.glyphLayout!!.linotype
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
batch.color = mainColObj
|
batch.color = mainColObj
|
||||||
pixmapTextureHolder = Texture(linotype)
|
|
||||||
|
|
||||||
if (!flipY) {
|
if (!flipY) {
|
||||||
batch.draw(pixmapTextureHolder, x.toFloat(), (y - pixmapOffsetY).toFloat())
|
batch.draw(tempLinotype, x.toFloat(), (y - pixmapOffsetY).toFloat())
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
batch.draw(pixmapTextureHolder,
|
batch.draw(tempLinotype,
|
||||||
x.toFloat(),
|
x.toFloat(),
|
||||||
(y - pixmapOffsetY + (pixmapTextureHolder?.height ?: 0)).toFloat(),
|
(y - pixmapOffsetY + (tempLinotype.height)).toFloat(),
|
||||||
(pixmapTextureHolder?.width?.toFloat()) ?: 0f,
|
(tempLinotype.width.toFloat()),
|
||||||
-(pixmapTextureHolder?.height?.toFloat() ?: 0f)
|
-(tempLinotype.height.toFloat())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -855,7 +828,7 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo
|
|||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
super.dispose()
|
super.dispose()
|
||||||
|
textCache.forEach { it.dispose() }
|
||||||
sheets.forEach { it.dispose() }
|
sheets.forEach { it.dispose() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1161,11 +1134,12 @@ class GameFontBase(fontDir: String, val noShadow: Boolean = false, val flipY: Bo
|
|||||||
fun getWidth(text: String) = getWidth(text.toCodePoints())
|
fun getWidth(text: String) = getWidth(text.toCodePoints())
|
||||||
|
|
||||||
fun getWidth(s: CodepointSequence): Int {
|
fun getWidth(s: CodepointSequence): Int {
|
||||||
try {
|
val cacheObj = getCache(s.getHash())
|
||||||
val cacheObj = getCache(s.getHash())
|
|
||||||
|
if (cacheObj != null) {
|
||||||
return cacheObj.glyphLayout!!.width
|
return cacheObj.glyphLayout!!.width
|
||||||
}
|
}
|
||||||
catch (e: NullPointerException) {
|
else {
|
||||||
return buildWidthAndPosBuffers(s).first.last()
|
return buildWidthAndPosBuffers(s).first.last()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user