From 050dddcca7f6b22ceeffab6b4384af3d5fb8bea9 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Wed, 29 Mar 2023 14:40:53 +0700 Subject: [PATCH] =?UTF-8?q?=D0=AD=D0=BA=D1=81=D0=BF=D0=B5=D1=80=D0=B8?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D1=81=D0=B0=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20StringInterner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru/dbotthepony/kstarbound/Starbound.kt | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 5fdee2a5..ba6dbe36 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -5,7 +5,9 @@ import com.github.benmanes.caffeine.cache.Caffeine import com.github.benmanes.caffeine.cache.Interner import com.google.gson.* import com.google.gson.internal.bind.JsonTreeReader +import it.unimi.dsi.fastutil.Hash import it.unimi.dsi.fastutil.objects.Object2ObjectFunction +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import org.apache.logging.log4j.LogManager import org.lwjgl.stb.STBImage @@ -70,6 +72,7 @@ import ru.dbotthepony.kstarbound.util.WriteOnce import ru.dbotthepony.kstarbound.util.traverseJsonPath import ru.dbotthepony.kvector.vector.nint.Vector2i import java.io.* +import java.lang.ref.ReferenceQueue import java.lang.ref.WeakReference import java.text.DateFormat import java.time.Duration @@ -805,3 +808,75 @@ class Starbound : ISBFileLocator { private val polyfill by lazy { loadInternalScript("polyfill") } } } + +private class StringInterner(private val segmentBits: Int) : Interner, Hash.Strategy { + class Ref(referent: String, queue: ReferenceQueue) : WeakReference(referent, queue) { + val hash = referent.hashCode() + + override fun hashCode(): Int { + return hash + } + } + + override fun equals(a: Any?, b: Any?): Boolean { + if (a is String && b is Ref) return a == b.get() + if (a is Ref && b is String) return a.get() == b + return a === b + } + + override fun hashCode(o: Any): Int { + return o.hashCode() + } + + private val queue = ReferenceQueue() + private val actualSegmentBits: Int + + init { + var result = 0 + + for (i in 0 until segmentBits) { + result = result or (1.shl(i)) + } + + actualSegmentBits = result + } + + private val cleaner = Runnable { + while (true) { + val ref = queue.remove() as Ref + val segment = segments[ref.hash and actualSegmentBits] + + synchronized(segment) { + val removed = segment.remove(ref) + check(removed === ref) { "Expected to remove reference $ref from segment ${ref.hash and actualSegmentBits} (full hash: ${ref.hash}), but we removed $removed (removed hash: ${removed.hashCode()}, removed segment: ${removed.hashCode() and actualSegmentBits})" } + } + } + } + + private val thread = Thread(cleaner, "String Interner Cleanup Thread") + + init { + thread.priority = 2 + thread.isDaemon = true + thread.start() + } + + private val segments: Array> = Array(1.shl(segmentBits)) { Object2ObjectOpenCustomHashMap(this) } + + override fun intern(sample: String): String { + val hash = sample.hashCode() + val segment = segments[hash and actualSegmentBits] + + synchronized(segment) { + val canonical = (segment[sample] as Ref?)?.get() + + if (canonical != null) { + return canonical + } + + val ref = Ref(sample, queue) + segment.put(ref, ref) + return sample + } + } +}