Skip to content

Commit a1bd9b9

Browse files
committed
fix neoforge server freeze
1 parent 8a819f0 commit a1bd9b9

File tree

11 files changed

+141
-42
lines changed

11 files changed

+141
-42
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ makeevrserg.java.ktarget=21
1313
# Project
1414
makeevrserg.project.name=SoulKeeper
1515
makeevrserg.project.group=ru.astrainteractive.soulkeeper
16-
makeevrserg.project.version.string=1.2.4
16+
makeevrserg.project.version.string=1.2.5
1717
makeevrserg.project.description=Keep your items after death
1818
makeevrserg.project.developers=makeevrserg|Makeev Roman|[email protected]
1919
makeevrserg.project.url=https://github.com/Astra-Interactive/SoulKeeper

instances/neoforge/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ val shadowJar by tasks.getting(ShadowJar::class) {
8383
// Dependencies
8484
exclude(dependency("org.jetbrains:annotations"))
8585
// Root
86+
exclude("kotlin/**") // use kotlin-neoforge
8687
exclude("_COROUTINE/**")
8788
exclude("DebugProbesKt.bin")
8889
exclude("jetty-dir.css")

modules/dao/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/SoulsDaoModule.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import kotlinx.coroutines.CoroutineScope
44
import kotlinx.coroutines.GlobalScope
55
import kotlinx.coroutines.flow.Flow
66
import kotlinx.coroutines.flow.SharingStarted
7-
import kotlinx.coroutines.flow.first
7+
import kotlinx.coroutines.flow.firstOrNull
88
import kotlinx.coroutines.flow.flow
99
import kotlinx.coroutines.flow.shareIn
1010
import kotlinx.coroutines.launch
@@ -55,7 +55,7 @@ interface SoulsDaoModule {
5555
override val lifecycle: Lifecycle = Lifecycle.Lambda(
5656
onDisable = {
5757
GlobalScope.launch(dispatchers.IO) {
58-
TransactionManager.closeAndUnregister(databaseFlow.first())
58+
databaseFlow.firstOrNull()?.let(TransactionManager::closeAndUnregister)
5959
}
6060
}
6161
)

modules/service-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/BukkitPlatformServiceModule.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class BukkitPlatformServiceModule(
3535
override val showArmorStandUseCase: ShowArmorStandUseCase = ShowArmorStandUseCaseFactory(coreModule).create()
3636
override val pickUpItemsUseCase: PickUpItemsUseCase = BukkitPickUpItemsUseCase(
3737
collectItemSoundProvider = { coreModule.soulsConfigKrate.cachedValue.sounds.collectItem },
38-
soulsDao = soulsDaoModule.soulsDao
38+
soulsDao = soulsDaoModule.soulsDao,
39+
dispatchers = coreModule.dispatchers
3940
)
4041
}

modules/service-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/domain/BukkitPickUpItemsUseCase.kt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package ru.astrainteractive.soulkeeper.module.souls.domain
22

3+
import kotlinx.coroutines.withContext
34
import org.bukkit.Bukkit
45
import ru.astrainteractive.astralibs.server.player.OnlineMinecraftPlayer
6+
import ru.astrainteractive.klibs.mikro.core.dispatchers.KotlinDispatchers
57
import ru.astrainteractive.klibs.mikro.core.logging.JUtiltLogger
68
import ru.astrainteractive.klibs.mikro.core.logging.Logger
79
import ru.astrainteractive.soulkeeper.core.plugin.SoulsConfig
@@ -16,6 +18,7 @@ import ru.astrainteractive.soulkeeper.module.souls.domain.PickUpItemsUseCase.Out
1618
internal class BukkitPickUpItemsUseCase(
1719
private val collectItemSoundProvider: () -> SoulsConfig.Sounds.SoundConfig,
1820
private val soulsDao: SoulsDao,
21+
private val dispatchers: KotlinDispatchers
1922
) : PickUpItemsUseCase,
2023
Logger by JUtiltLogger("SoulKeeper-PickUpItemsUseCase") {
2124

@@ -27,10 +30,23 @@ internal class BukkitPickUpItemsUseCase(
2730
.map(StringFormatObject::raw)
2831
.map(ItemStackSerializer::decodeFromString)
2932
.mapNotNull { itemStackResult -> itemStackResult.getOrNull() }
30-
val notAddedItems = bukkitPlayer.inventory.addItem(*items.toTypedArray()).values.toList()
33+
val notAddedItems = withContext(dispatchers.Main) {
34+
bukkitPlayer.inventory
35+
.addItem(*items.toTypedArray())
36+
.values
37+
.toList()
38+
}
3139
if (notAddedItems != soul.items) {
32-
soul.location.toBukkitLocation().playSoundForPlayer(bukkitPlayer, collectItemSoundProvider.invoke())
40+
withContext(dispatchers.Main) {
41+
soul.location
42+
.toBukkitLocation()
43+
.playSoundForPlayer(
44+
player = bukkitPlayer,
45+
sound = collectItemSoundProvider.invoke()
46+
)
47+
}
3348
}
49+
3450
soulsDao.updateSoul(
3551
soul.copy(
3652
items = notAddedItems

modules/service-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/NeoForgePlatformServiceModule.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class NeoForgePlatformServiceModule(
3333
collectItemSoundProvider = { coreModule.soulsConfigKrate.cachedValue.sounds.collectItem },
3434
soulsDao = soulsDaoModule.soulsDao,
3535
effectEmitter = effectEmitter,
36-
isDeadPlayerProvider = isDeadPlayerProvider
36+
isDeadPlayerProvider = isDeadPlayerProvider,
37+
dispatchers = coreModule.dispatchers
3738
)
3839
}

modules/service-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/domain/NeoForgePickUpItemsUseCase.kt

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package ru.astrainteractive.soulkeeper.module.souls.domain
22

3+
import kotlinx.coroutines.withContext
34
import net.minecraft.world.item.ItemStack
45
import ru.astrainteractive.astralibs.server.player.OnlineMinecraftPlayer
56
import ru.astrainteractive.astralibs.server.util.NeoForgeUtil
67
import ru.astrainteractive.astralibs.server.util.getOnlinePlayer
8+
import ru.astrainteractive.klibs.mikro.core.dispatchers.KotlinDispatchers
79
import ru.astrainteractive.klibs.mikro.core.logging.JUtiltLogger
810
import ru.astrainteractive.klibs.mikro.core.logging.Logger
911
import ru.astrainteractive.soulkeeper.core.plugin.SoulsConfig
@@ -19,7 +21,8 @@ internal class NeoForgePickUpItemsUseCase(
1921
private val collectItemSoundProvider: () -> SoulsConfig.Sounds.SoundConfig,
2022
private val soulsDao: SoulsDao,
2123
private val effectEmitter: EffectEmitter,
22-
private val isDeadPlayerProvider: IsDeadPlayerProvider
24+
private val isDeadPlayerProvider: IsDeadPlayerProvider,
25+
private val dispatchers: KotlinDispatchers
2326
) : PickUpItemsUseCase,
2427
Logger by JUtiltLogger("SoulKeeper-PickUpItemsUseCase") {
2528
/**
@@ -47,14 +50,22 @@ internal class NeoForgePickUpItemsUseCase(
4750
if (serverPlayer.gameMode.isCreative) return Output.SomeItemsRemain
4851
if (isDeadPlayerProvider.isDead(player)) return Output.SomeItemsRemain
4952

50-
val notAddedItems = player.addItems(
51-
items = soul.items
52-
.map(StringFormatObject::raw)
53-
.map(ItemStackSerializer::decodeFromString)
54-
.mapNotNull { result -> result.getOrNull() }
55-
)
53+
val notAddedItems = withContext(dispatchers.Main) {
54+
player.addItems(
55+
items = soul.items
56+
.map(StringFormatObject::raw)
57+
.map(ItemStackSerializer::decodeFromString)
58+
.mapNotNull { result -> result.getOrNull() }
59+
)
60+
}
5661
if (notAddedItems.isEmpty()) {
57-
effectEmitter.playSoundForPlayer(soul.location, player, collectItemSoundProvider.invoke())
62+
withContext(dispatchers.Main) {
63+
effectEmitter.playSoundForPlayer(
64+
location = soul.location,
65+
player = player,
66+
sound = collectItemSoundProvider.invoke()
67+
)
68+
}
5869
}
5970
soulsDao.updateSoul(
6071
soul.copy(
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package ru.astrainteractive.soulkeeper.core.service
2+
3+
import kotlinx.coroutines.Dispatchers
4+
import kotlinx.coroutines.Job
5+
import kotlinx.coroutines.cancel
6+
import kotlinx.coroutines.flow.Flow
7+
import kotlinx.coroutines.flow.channelFlow
8+
import kotlinx.coroutines.flow.collectLatest
9+
import kotlinx.coroutines.flow.combine
10+
import kotlinx.coroutines.flow.flattenConcat
11+
import kotlinx.coroutines.flow.flowOf
12+
import kotlinx.coroutines.flow.launchIn
13+
import kotlinx.coroutines.launch
14+
import ru.astrainteractive.astralibs.coroutines.withTimings
15+
import ru.astrainteractive.astralibs.service.Service
16+
import ru.astrainteractive.astralibs.service.ServiceExecutor
17+
import ru.astrainteractive.klibs.mikro.core.coroutines.CoroutineFeature
18+
import ru.astrainteractive.klibs.mikro.core.coroutines.TickFlow
19+
import kotlin.coroutines.CoroutineContext
20+
import kotlin.time.Duration
21+
22+
class ThrottleTickFlowService(
23+
coroutineContext: CoroutineContext = Dispatchers.IO,
24+
private val delay: Flow<Duration>,
25+
private val initialDelay: Flow<Duration> = flowOf(Duration.ZERO),
26+
private val executor: ServiceExecutor
27+
) : Service {
28+
private val scope = CoroutineFeature
29+
.Default(coroutineContext)
30+
.withTimings()
31+
32+
fun <T, K> Flow<T>.throttleLatest(
33+
transform: suspend (T) -> K
34+
): Flow<K> = channelFlow {
35+
val scope = this
36+
var job: Job? = null
37+
38+
collectLatest { value ->
39+
job?.join()
40+
job = scope.launch {
41+
send(transform(value))
42+
}
43+
}
44+
}
45+
46+
private val lazyTickFlow = lazy {
47+
combine(delay, initialDelay, ::TickFlow)
48+
.flattenConcat()
49+
.throttleLatest { executor.doWork() }
50+
.launchIn(scope)
51+
}
52+
53+
override fun onCreate() {
54+
lazyTickFlow.value
55+
}
56+
57+
override fun onDestroy() {
58+
lazyTickFlow.value.cancel()
59+
scope.cancel()
60+
}
61+
}

modules/service/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/ServiceModule.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ package ru.astrainteractive.soulkeeper.module.souls.di
22

33
import kotlinx.coroutines.flow.flowOf
44
import ru.astrainteractive.astralibs.lifecycle.Lifecycle
5-
import ru.astrainteractive.astralibs.service.TickFlowService
65
import ru.astrainteractive.soulkeeper.core.di.CoreModule
6+
import ru.astrainteractive.soulkeeper.core.service.ThrottleTickFlowService
77
import ru.astrainteractive.soulkeeper.module.souls.domain.GetNearestSoulUseCase
88
import ru.astrainteractive.soulkeeper.module.souls.domain.PickUpExpUseCase
99
import ru.astrainteractive.soulkeeper.module.souls.domain.PickUpSoulUseCase
@@ -39,7 +39,7 @@ class ServiceModule(
3939
effectEmitter = platformServiceModule.effectEmitter
4040
)
4141

42-
private val deleteSoulService = TickFlowService(
42+
private val deleteSoulService = ThrottleTickFlowService(
4343
coroutineContext = coreModule.dispatchers.IO,
4444
delay = flowOf(60.seconds),
4545
executor = DeleteSoulWorker(
@@ -48,7 +48,7 @@ class ServiceModule(
4848
)
4949
)
5050

51-
private val freeSoulService = TickFlowService(
51+
private val freeSoulService = ThrottleTickFlowService(
5252
coroutineContext = coreModule.dispatchers.IO,
5353
delay = flowOf(60.seconds),
5454
executor = FreeSoulWorker(
@@ -71,9 +71,10 @@ class ServiceModule(
7171
collectXpSoundProvider = { coreModule.soulsConfigKrate.cachedValue.sounds.collectXp },
7272
soulsDao = soulsDaoModule.soulsDao,
7373
effectEmitter = platformServiceModule.effectEmitter,
74-
experiencedFactory = platformServiceModule.onlineMinecraftPlayerExperiencedFactory
74+
experiencedFactory = platformServiceModule.onlineMinecraftPlayerExperiencedFactory,
75+
dispatchers = coreModule.dispatchers
7576
)
76-
private val pickUpSoulService = TickFlowService(
77+
private val pickUpSoulService = ThrottleTickFlowService(
7778
coroutineContext = coreModule.dispatchers.IO,
7879
delay = flowOf(3.seconds),
7980
executor = PickUpWorker(

modules/service/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/domain/PickUpExpUseCase.kt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package ru.astrainteractive.soulkeeper.module.souls.domain
22

3+
import kotlinx.coroutines.withContext
34
import ru.astrainteractive.astralibs.server.player.OnlineMinecraftPlayer
5+
import ru.astrainteractive.klibs.mikro.core.dispatchers.KotlinDispatchers
46
import ru.astrainteractive.klibs.mikro.core.logging.JUtiltLogger
57
import ru.astrainteractive.klibs.mikro.core.logging.Logger
68
import ru.astrainteractive.soulkeeper.core.plugin.SoulsConfig
@@ -13,6 +15,7 @@ class PickUpExpUseCase(
1315
private val collectXpSoundProvider: () -> SoulsConfig.Sounds.SoundConfig,
1416
private val soulsDao: SoulsDao,
1517
private val effectEmitter: EffectEmitter,
18+
private val dispatchers: KotlinDispatchers,
1619
private val experiencedFactory: Experienced.Factory<OnlineMinecraftPlayer>
1720
) : Logger by JUtiltLogger("PickUpExpUseCase") {
1821
sealed interface Output {
@@ -22,12 +25,14 @@ class PickUpExpUseCase(
2225

2326
suspend fun invoke(player: OnlineMinecraftPlayer, soul: ItemDatabaseSoul): Output {
2427
if (soul.exp <= 0) return Output.NoExpPresent
25-
effectEmitter.playSoundForPlayer(
26-
location = soul.location,
27-
player = player,
28-
sound = collectXpSoundProvider.invoke()
29-
)
30-
experiencedFactory.create(player).giveExperience(soul.exp)
28+
withContext(dispatchers.Main) {
29+
effectEmitter.playSoundForPlayer(
30+
location = soul.location,
31+
player = player,
32+
sound = collectXpSoundProvider.invoke()
33+
)
34+
experiencedFactory.create(player).giveExperience(soul.exp)
35+
}
3136
soulsDao.updateSoul(soul = soul.copy(exp = 0))
3237
return Output.ExpCollected
3338
}

0 commit comments

Comments
 (0)