Skip to content

Commit

Permalink
feat: Working somewhat efficient h2 cache
Browse files Browse the repository at this point in the history
  • Loading branch information
phinner committed Nov 13, 2024
1 parent 3300941 commit 4c9e639
Show file tree
Hide file tree
Showing 18 changed files with 542 additions and 60 deletions.
12 changes: 11 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ dependencies {
implementation(libs.hoplite.core)
implementation(libs.hoplite.yaml)
implementation(libs.guava)
implementation(libs.hikari) {
exclude("org.slf4j")
}

testImplementation(libs.junit.api)
testRuntimeOnly(libs.junit.engine)
Expand Down Expand Up @@ -156,6 +159,13 @@ val downloadSlf4md by tasks.registering(GithubAssetDownload::class) {
version = "v${libs.versions.slf4md.get()}"
}

val downloadSql4md by tasks.registering(GithubAssetDownload::class) {
owner = "xpdustry"
repo = "sql4md"
asset = "sql4md.jar"
version = "v${libs.versions.sql4md.get()}"
}

val downloadKotlinRuntime by tasks.registering(GithubAssetDownload::class) {
owner = "xpdustry"
repo = "kotlin-runtime"
Expand All @@ -164,5 +174,5 @@ val downloadKotlinRuntime by tasks.registering(GithubAssetDownload::class) {
}

tasks.runMindustryServer {
mods.from(downloadSlf4md, downloadKotlinRuntime)
mods.from(downloadSlf4md, downloadSql4md, downloadKotlinRuntime)
}
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ dokka = "1.9.20"

# utilities
slf4md = "1.0.1"
sql4md = "1.1.0"
slf4j = "2.0.16"
hoplite = "2.8.2"
okhttp = "4.12.0"
guava = "33.3.1-jre"
hikari = "6.1.0"

# testing
junit = "5.11.2"
Expand All @@ -36,6 +38,7 @@ hoplite-core = { module = "com.sksamuel.hoplite:hoplite-core", version.ref = "ho
hoplite-yaml = { module = "com.sksamuel.hoplite:hoplite-yaml", version.ref = "hoplite" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
hikari = { module = "com.zaxxer:HikariCP", version.ref = "hikari" }

# testing
junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
Expand Down
4 changes: 2 additions & 2 deletions plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
"name": "nohorny",
"displayName": "NoHorny",
"description": "NO HORNY IN MY SERVER!",
"version": "3.0.0",
"version": "3.0.0-beta.1",
"author": "Xpdustry",
"repo": "xpdustry/nohorny",
"main": "com.xpdustry.nohorny.NoHornyPlugin",
"java": true,
"hidden": true,
"minGameVersion": "146",
"dependencies": [ "kotlin-runtime", "slf4md" ]
"dependencies": [ "kotlin-runtime", "slf4md", "sql4md" ]
}
1 change: 1 addition & 0 deletions src/main/kotlin/com/xpdustry/nohorny/NoHornyAPI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*/
package com.xpdustry.nohorny

import com.xpdustry.nohorny.cache.NoHornyCache
import mindustry.Vars

public interface NoHornyAPI {
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/com/xpdustry/nohorny/NoHornyAutoBan.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
*/
package com.xpdustry.nohorny

import com.xpdustry.nohorny.analyzer.ImageAnalyzer
import com.xpdustry.nohorny.analyzer.ImageAnalyzerEvent
import com.xpdustry.nohorny.analyzer.ImageInformation
import com.xpdustry.nohorny.extension.onEvent
import com.xpdustry.nohorny.geometry.ImmutablePoint
import mindustry.Vars
Expand All @@ -40,7 +40,7 @@ import mindustry.world.blocks.logic.LogicDisplay
internal class NoHornyAutoBan(private val plugin: NoHornyPlugin) : NoHornyListener {
override fun onInit() {
onEvent<ImageAnalyzerEvent> { (result, cluster, _, author) ->
if (result.rating == ImageAnalyzer.Rating.UNSAFE &&
if (result.rating == ImageInformation.Rating.UNSAFE &&
plugin.config.autoBan &&
author != null
) {
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/com/xpdustry/nohorny/NoHornyConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
package com.xpdustry.nohorny

import com.sksamuel.hoplite.Secret
import com.xpdustry.nohorny.analyzer.ImageAnalyzer
import com.xpdustry.nohorny.analyzer.ImageInformation
import com.xpdustry.nohorny.tracker.CanvasesConfig
import com.xpdustry.nohorny.tracker.DisplaysConfig
import kotlin.time.Duration
Expand All @@ -53,7 +53,7 @@ internal data class NoHornyConfig(
val sightEngineSecret: Secret,
val unsafeThreshold: Float = 0.55F,
val warningThreshold: Float = 0.4F,
val kinds: List<ImageAnalyzer.Kind> = listOf(ImageAnalyzer.Kind.NUDITY),
val kinds: List<ImageInformation.Kind> = listOf(ImageInformation.Kind.NUDITY),
) : Analyzer {
init {
require(unsafeThreshold >= 0) { "unsafeThreshold cannot be lower than 0" }
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/xpdustry/nohorny/NoHornyCoroutines.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.ExecutorService

internal class NoHornyCoroutines(executor: ExecutorService) {
val dispatcher = executor.asCoroutineDispatcher()
private val dispatcher = executor.asCoroutineDispatcher()
val job = SupervisorJob()
val displays = CoroutineScope(dispatcher.limitedParallelism(1) + job + CoroutineName("Displays Scope"))
val canvases = CoroutineScope(dispatcher.limitedParallelism(1) + job + CoroutineName("Canvases Scope"))
Expand Down
53 changes: 43 additions & 10 deletions src/main/kotlin/com/xpdustry/nohorny/NoHornyPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,30 +37,33 @@ import com.xpdustry.nohorny.analyzer.FallbackAnalyzer
import com.xpdustry.nohorny.analyzer.ImageAnalyzer
import com.xpdustry.nohorny.analyzer.ImageAnalyzerEvent
import com.xpdustry.nohorny.analyzer.SightEngineAnalyzer
import com.xpdustry.nohorny.cache.NoHornyCache
import com.xpdustry.nohorny.cache.SQLCache
import com.xpdustry.nohorny.geometry.BlockGroup
import com.xpdustry.nohorny.tracker.CanvasesTracker
import com.xpdustry.nohorny.tracker.DisplaysTracker
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import mindustry.Vars
import mindustry.mod.Plugin
import okhttp3.Dispatcher
import okhttp3.OkHttpClient
import org.slf4j.LoggerFactory
import java.lang.Runnable
import java.nio.file.Files
import java.nio.file.Path
import java.util.concurrent.Executors
import java.util.concurrent.ThreadFactory
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import kotlin.io.path.absolutePathString
import kotlin.io.path.notExists
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration

public class NoHornyPlugin : Plugin(), NoHornyAPI {
private val directory: Path = Vars.modDirectory.child("nohorny").file().toPath()
private val directory = Vars.modDirectory.child("nohorny").file().toPath()
private val file = directory.resolve("config.yaml")
private val listeners = mutableListOf<NoHornyListener>()
private val executor = Executors.newScheduledThreadPool(4, NoHornyThreadFactory)
Expand Down Expand Up @@ -89,17 +92,30 @@ public class NoHornyPlugin : Plugin(), NoHornyAPI {
internal val coroutines = NoHornyCoroutines(executor)
internal var config = NoHornyConfig()
internal var analyzer: ImageAnalyzer = ImageAnalyzer.None
internal var cache: NoHornyCache = NoHornyCache.None
private lateinit var hikari: HikariDataSource
private var cache: NoHornyCache = SQLCache({ hikari }, coroutines)

init {
Files.createDirectories(directory)
listeners += cache as NoHornyListener
listeners += CanvasesTracker(this)
listeners += DisplaysTracker(this)
listeners += NoHornyAutoBan(this)
}

override fun init() {
reload()

hikari =
HikariDataSource(
HikariConfig().apply {
jdbcUrl = "jdbc:h2:${directory.resolve("database.h2").absolutePathString()};MODE=MYSQL"
poolName = "no-horny-jdbc-pool"
maximumPoolSize = 4
minimumIdle = 1
},
)

listeners.forEach(NoHornyListener::onInit)
logger.info("Initialized no-horny, to the horny jail we go.")

Expand All @@ -110,8 +126,7 @@ public class NoHornyPlugin : Plugin(), NoHornyAPI {
if (!executor.awaitTermination(10L, TimeUnit.SECONDS)) {
executor.shutdownNow()
}
coroutines.job.complete()
runBlocking { coroutines.job.join() }
hikari.close()
}
},
)
Expand Down Expand Up @@ -163,14 +178,32 @@ public class NoHornyPlugin : Plugin(), NoHornyAPI {

internal fun process(group: BlockGroup<out NoHornyImage>) =
coroutines.global.launch {
logger.trace("Processing group at ({}, {})", group.x, group.y)
val image = renderer.render(group)
var store = false
val result =
cache.getResult(group, image).await() ?: run {
store = true
analyzer.analyse(image).await()
var result =
try {
cache.getResult(group, image).await()
} catch (e: Exception) {
logger.error("Failed to get cached result for group at (${group.x}, ${group.y})", e)
null
}
if (result == null) {
logger.trace("Cache miss for group at ({}, {})", group.x, group.y)
store = true
try {
result = analyzer.analyse(image).await()!!
} catch (e: Exception) {
logger.error("Failed to analyse image for group at (${group.x}, ${group.y})", e)
return@launch
}
} else {
logger.trace("Cache hit for group at ({}, {})", group.x, group.y)
}

logger.trace("Result for group at ({}, {}): {}", group.x, group.y, result)
if (store) {
logger.trace("Storing result for group at ({}, {})", group.x, group.y)
cache.putResult(group, image, result)
}
Core.app.post {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import java.util.concurrent.CompletableFuture
import kotlin.io.path.writeBytes

internal class DebugImageAnalyzer(private val directory: Path) : ImageAnalyzer {
override fun analyse(image: BufferedImage): CompletableFuture<ImageAnalyzer.Result> {
override fun analyse(image: BufferedImage): CompletableFuture<ImageInformation> {
try {
directory.toFile().mkdirs()
directory
Expand All @@ -42,6 +42,6 @@ internal class DebugImageAnalyzer(private val directory: Path) : ImageAnalyzer {
} catch (error: IOException) {
return CompletableFuture.failedFuture(error)
}
return CompletableFuture.completedFuture(ImageAnalyzer.Result.EMPTY)
return CompletableFuture.completedFuture(ImageInformation.EMPTY)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import java.util.concurrent.CompletableFuture

internal class FallbackAnalyzer(private val primary: ImageAnalyzer, private val secondary: ImageAnalyzer) :
ImageAnalyzer {
override fun analyse(image: BufferedImage): CompletableFuture<ImageAnalyzer.Result> =
override fun analyse(image: BufferedImage): CompletableFuture<ImageInformation> =
primary.analyse(image).exceptionallyCompose { throwable ->
LOGGER.debug("Primary analyzer failed, switching to secondary", throwable)
secondary.analyse(image)
Expand Down
24 changes: 5 additions & 19 deletions src/main/kotlin/com/xpdustry/nohorny/analyzer/ImageAnalyzer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,12 @@ import java.awt.image.BufferedImage
import java.util.concurrent.CompletableFuture

public interface ImageAnalyzer {
public fun analyse(image: BufferedImage): CompletableFuture<Result>
public fun analyse(image: BufferedImage): CompletableFuture<ImageInformation>

public object None : ImageAnalyzer {
override fun analyse(image: BufferedImage): CompletableFuture<Result> = CompletableFuture.completedFuture(Result.EMPTY)
}

public data class Result(val rating: Rating, val details: Map<Kind, Float>) {
public companion object {
@JvmField public val EMPTY: Result = Result(Rating.SAFE, emptyMap())
}
}

public enum class Kind {
NUDITY,
GORE,
}

public enum class Rating {
SAFE,
WARNING,
UNSAFE,
override fun analyse(image: BufferedImage): CompletableFuture<ImageInformation> =
CompletableFuture.completedFuture(
ImageInformation.EMPTY,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import com.xpdustry.nohorny.geometry.BlockGroup
import java.awt.image.BufferedImage

public data class ImageAnalyzerEvent(
val result: ImageAnalyzer.Result,
val result: ImageInformation,
val group: BlockGroup<out NoHornyImage>,
val image: BufferedImage,
) {
Expand Down
48 changes: 48 additions & 0 deletions src/main/kotlin/com/xpdustry/nohorny/analyzer/ImageInformation.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* This file is part of NoHorny. The plugin securing your server against nsfw builds.
*
* MIT License
*
* Copyright (c) 2024 Xpdustry
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.xpdustry.nohorny.analyzer

import kotlinx.serialization.Serializable

@Serializable
public data class ImageInformation(val rating: Rating, val details: Map<Kind, Float>) {
@Serializable
public enum class Kind {
NUDITY,
GORE,
}

@Serializable
public enum class Rating {
SAFE,
WARNING,
UNSAFE,
}

public companion object {
@JvmField public val EMPTY: ImageInformation = ImageInformation(Rating.SAFE, emptyMap())
}
}
Loading

0 comments on commit 4c9e639

Please sign in to comment.