Skip to content

Commit 57d24cb

Browse files
committed
feat: implement defer loading
1 parent 20c0642 commit 57d24cb

File tree

9 files changed

+809
-440
lines changed

9 files changed

+809
-440
lines changed

src/main/kotlin/be4rjp/sclat/Sclat.kt

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import be4rjp.sclat.manager.ColorMgr
2929
import be4rjp.sclat.manager.GameMgr
3030
import be4rjp.sclat.manager.MainWeaponMgr
3131
import be4rjp.sclat.manager.MapDataMgr
32+
import be4rjp.sclat.manager.MapLoader
3233
import be4rjp.sclat.manager.MatchMgr
3334
import be4rjp.sclat.manager.NoteBlockAPIMgr
3435
import be4rjp.sclat.manager.PlayerReturnManager
@@ -102,11 +103,18 @@ class Sclat :
102103
conf = Config()
103104
conf?.loadConfig()
104105
NewConfig.load()
105-
for (mapname in conf!!.mapConfig!!.getConfigurationSection("Maps")!!.getKeys(false)) {
106-
val worldName: String? = conf!!.mapConfig!!.getString("Maps." + mapname + ".WorldName")
107-
Bukkit.createWorld(WorldCreator(worldName!!))
108-
val world = Bukkit.getWorld(worldName)
109-
world!!.isAutoSave = false
106+
// Map world creation is deferred by default. Use the config toggle
107+
// `deferredMapLoading` (default true) to control the behavior.
108+
val deferred = conf!!.config!!.getBoolean("deferredMapLoading", true)
109+
if (!deferred) {
110+
for (mapname in conf!!.mapConfig!!.getConfigurationSection("Maps")!!.getKeys(false)) {
111+
val worldName: String? = conf!!.mapConfig!!.getString("Maps." + mapname + ".WorldName")
112+
Bukkit.createWorld(WorldCreator(worldName!!))
113+
val world = Bukkit.getWorld(worldName)
114+
world!!.isAutoSave = false
115+
}
116+
} else {
117+
sclatLogger.info("Deferred map loading is enabled; maps will be loaded when assigned to matches.")
110118
}
111119
if (conf!!.config!!.contains("Tutorial")) tutorial = conf!!.config!!.getBoolean("Tutorial")
112120
if (conf!!.config!!.contains("Colors")) colors = conf!!.config!!.getStringList("Colors")
@@ -323,9 +331,21 @@ class Sclat :
323331

324332
// ------------------------Tutorial wire mesh-------------------------
325333
if (tutorial) {
334+
val eagerTutorial = conf!!.config!!.getBoolean("eagerLoadTutorialMaps", true)
335+
if (eagerTutorial) {
336+
// Preload tutorial maps so their runtime objects and wiremesh tasks exist.
337+
for (mData in DataMgr.maplist) {
338+
try {
339+
MapLoader.incrementUsage(mData)
340+
} catch (e: Exception) {
341+
}
342+
}
343+
}
344+
326345
for (mData in DataMgr.maplist) {
327-
for (wiremesh in mData.wiremeshListTask!!.wiremeshsList) {
328-
wiremesh!!.startTask()
346+
try {
347+
mData.wiremeshListTask?.wiremeshsList?.forEach { it?.startTask() }
348+
} catch (e: Exception) {
329349
}
330350
}
331351
}
@@ -402,12 +422,11 @@ class Sclat :
402422

403423
for (`as` in DataMgr.al) `as`!!.remove()
404424

405-
// Worldが保存される前にアンロードして塗られた状態で保存されるのを防ぐ
406-
if (type == ServerType.LOBBY) {
407-
for (mapname in conf!!.mapConfig!!.getConfigurationSection("Maps")!!.getKeys(false)) {
408-
val worldName: String? = conf!!.mapConfig!!.getString("Maps." + mapname + ".WorldName")
409-
Bukkit.unloadWorld(worldName!!, false)
410-
}
425+
// Unload all loaded maps using MapLoader so it can perform proper cleanup.
426+
// This replaces the previous eager world-unload loop.
427+
try {
428+
MapLoader.unloadAllLoadedMaps()
429+
} catch (e: Exception) {
411430
}
412431

413432
if (type == ServerType.LOBBY) {
Lines changed: 84 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,117 @@
11
package be4rjp.sclat.api.wiremesh
22

33
import be4rjp.sclat.data.RegionBlocks
4+
import be4rjp.sclat.plugin
5+
import be4rjp.sclat.sclatLogger
46
import org.bukkit.Location
57
import org.bukkit.Material
68
import org.bukkit.block.Block
79
import org.bukkit.block.data.BlockData
8-
import java.util.function.Consumer
10+
import org.bukkit.scheduler.BukkitRunnable
911

10-
/**
11-
*
12-
* @author Be4rJP
13-
*/
1412
class WiremeshListTask(
1513
private val firstPoint: Location,
1614
private val secondPoint: Location,
1715
trapDoor: Boolean,
1816
ironBars: Boolean,
1917
fence: Boolean,
2018
) {
21-
private val blockList: MutableList<Block> = ArrayList<Block>()
19+
private val blockList: MutableList<Block> = ArrayList()
2220

2321
@JvmField
24-
val wiremeshsList: MutableList<Wiremesh?> = ArrayList<Wiremesh?>()
22+
val wiremeshsList: MutableList<Wiremesh?> = ArrayList()
2523
private val blockDataMap: MutableMap<Block, BlockData> = mutableMapOf()
2624

25+
private var builderTask: BukkitRunnable? = null
26+
27+
// stopRequested signals that a stop has been requested and the builder
28+
// should abort at the next convenient point. This avoids races between
29+
// the builder and MapLoader attempting to cancel it.
30+
private var stopRequested: Boolean = false
31+
val totalBlocks: Int
32+
2733
init {
28-
// 先に対象のブロックとそのBlockDataを取得して保存しておく
2934
val list = RegionBlocks(firstPoint, secondPoint).blocks
3035

3136
for (block in list) {
32-
if (!blockList.contains(block) &&
33-
(
34-
(block.type == Material.IRON_TRAPDOOR && trapDoor) ||
35-
(block.type == Material.IRON_BARS && ironBars) ||
36-
(block.type.toString().contains("FENCE") && fence)
37-
)
38-
) {
37+
val isCandidate =
38+
(block.type == Material.IRON_TRAPDOOR && trapDoor) ||
39+
(block.type == Material.IRON_BARS && ironBars) ||
40+
(block.type.toString().contains("FENCE") && fence)
41+
42+
if (!blockList.contains(block) && isCandidate) {
3943
val bData = block.blockData
40-
blockDataMap.put(block, bData)
44+
blockDataMap[block] = bData
4145
blockList.add(block)
4246
}
4347
}
4448

45-
// Wiremeshを作成してタスクを実行
46-
for (block in blockList) {
47-
val bData = blockDataMap.get(block)
48-
val wm = Wiremesh(block, block.type, bData!!)
49-
wiremeshsList.add(wm)
50-
}
49+
totalBlocks = blockList.size
50+
}
51+
52+
fun startBuilding(batchSize: Int = 100) {
53+
if (builderTask != null) return
54+
55+
builderTask =
56+
object : BukkitRunnable() {
57+
override fun run() {
58+
try {
59+
var processed = 0
60+
while (processed < batchSize && blockList.isNotEmpty() && !stopRequested) {
61+
val block = blockList.removeAt(0)
62+
val bData = blockDataMap[block] ?: continue
63+
val wm = Wiremesh(block, block.type, bData)
64+
try {
65+
wm.startTask()
66+
} catch (e: Exception) {
67+
// ignore
68+
}
69+
wiremeshsList.add(wm)
70+
processed++
71+
}
72+
73+
if (blockList.isEmpty() || stopRequested) {
74+
sclatLogger.info("WiremeshListTask: finished building wiremesh (count=${wiremeshsList.size})")
75+
this.cancel()
76+
builderTask = null
77+
}
78+
} catch (e: Exception) {
79+
e.printStackTrace()
80+
this.cancel()
81+
builderTask = null
82+
}
83+
}
84+
}
85+
86+
builderTask!!.runTaskTimer(plugin, 0L, 1L)
87+
sclatLogger.info("WiremeshListTask: started incremental build (total=$totalBlocks)")
5188
}
5289

5390
fun stopTask() {
54-
wiremeshsList.forEach(Consumer { obj: Wiremesh? -> obj!!.stopTask() })
91+
// Request the builder to stop and cancel scheduled runs. We keep the
92+
// method idempotent and resilient to races by cancelling again when
93+
// a watcher observes no more work.
94+
stopRequested = true
95+
try {
96+
builderTask?.cancel()
97+
} catch (_: Exception) {
98+
}
99+
// Clear the reference so callers checking builderTask know there's no
100+
// scheduled task left. The actual builder may still be running this
101+
// tick and may append a small number of additional Wiremesh objects;
102+
// callers should re-invoke stopTask() after waiting for the builder to
103+
// finish to ensure those are also stopped.
104+
builderTask = null
105+
106+
try {
107+
for (obj in wiremeshsList) obj?.stopTask()
108+
} catch (_: Exception) {
109+
}
55110
}
111+
112+
/**
113+
* Returns true when the builder is either scheduled or there are pending
114+
* candidate blocks to process.
115+
*/
116+
fun isWorking(): Boolean = (builderTask != null) || blockList.isNotEmpty()
56117
}

src/main/kotlin/be4rjp/sclat/commands/SclatCommandExecutor.kt

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import be4rjp.sclat.api.SoundType
1010
import be4rjp.sclat.data.DataMgr
1111
import be4rjp.sclat.emblem.EmblemManager
1212
import be4rjp.sclat.manager.BungeeCordMgr
13+
import be4rjp.sclat.manager.MapLoader
1314
import be4rjp.sclat.manager.ServerStatusManager
1415
import be4rjp.sclat.plugin
1516
import be4rjp.sclat.server.EquipmentClient
@@ -317,6 +318,115 @@ class SclatCommandExecutor :
317318
return true
318319
}
319320
}
321+
322+
// ------------------------/sclat map <cmd> <map>-------------------------
323+
if (args[0].equals("map", ignoreCase = true)) {
324+
if (type == CommanderType.MEMBER) {
325+
sender.sendMessage(ChatColor.RED.toString() + "You don't have permission.")
326+
if (sender is Player) playGameSound(sender, SoundType.ERROR)
327+
return true
328+
}
329+
330+
if (args.size < 2) return false
331+
val sub = args[1].lowercase(Locale.getDefault())
332+
when (sub) {
333+
"preload" -> {
334+
if (args.size < 3) return false
335+
val target = args[2]
336+
if (target == "all") {
337+
for (m in DataMgr.maplist) {
338+
try {
339+
MapLoader.incrementUsage(m)
340+
} catch (e: Exception) {
341+
}
342+
}
343+
sender.sendMessage("Preloaded all maps (requested)")
344+
return true
345+
}
346+
val map = DataMgr.maplist.find { it.mapName == target }
347+
if (map == null) {
348+
sender.sendMessage("Map not found: $target")
349+
return true
350+
}
351+
try {
352+
MapLoader.incrementUsage(map)
353+
sender.sendMessage("Preloaded map: $target")
354+
} catch (e: Exception) {
355+
sender.sendMessage("Failed to preload map: $target")
356+
}
357+
return true
358+
}
359+
360+
"unload" -> {
361+
if (args.size < 3) return false
362+
val target = args[2]
363+
if (target == "all") {
364+
try {
365+
MapLoader.unloadAllLoadedMaps()
366+
sender.sendMessage("Unload requested for all maps")
367+
} catch (e: Exception) {
368+
sender.sendMessage("Failed to request unload for all maps")
369+
}
370+
return true
371+
}
372+
val map = DataMgr.maplist.find { it.mapName == target }
373+
if (map == null) {
374+
sender.sendMessage("Map not found: $target")
375+
return true
376+
}
377+
try {
378+
MapLoader.attemptUnload(map, true)
379+
sender.sendMessage("Unload requested for map: $target")
380+
} catch (e: Exception) {
381+
sender.sendMessage("Failed to request unload for map: $target")
382+
}
383+
return true
384+
}
385+
386+
"status", "list" -> {
387+
if (args.size >= 3) {
388+
val target = args[2]
389+
val map = DataMgr.maplist.find { it.mapName == target }
390+
if (map == null) {
391+
sender.sendMessage("Map not found: $target")
392+
return true
393+
}
394+
val loaded = if (map.team0Loc != null || map.wiremeshListTask != null) "LOADED" else "UNLOADED"
395+
sender.sendMessage("$target : $loaded")
396+
return true
397+
}
398+
sender.sendMessage("Maps:")
399+
for (m in DataMgr.maplist) {
400+
val loaded = if (m.team0Loc != null || m.wiremeshListTask != null) "LOADED" else "UNLOADED"
401+
sender.sendMessage(" - ${m.mapName} : $loaded")
402+
}
403+
return true
404+
}
405+
406+
"metrics" -> {
407+
if (args.size < 3) return false
408+
val target = args[2]
409+
if (target == "all") {
410+
for (m in DataMgr.maplist) {
411+
val metrics = MapLoader.getMetricsString(m.mapName)
412+
sender.sendMessage("${m.mapName} : ${metrics ?: "no metrics"}")
413+
}
414+
return true
415+
}
416+
417+
val map = DataMgr.maplist.find { it.mapName == target }
418+
if (map == null) {
419+
sender.sendMessage("Map not found: $target")
420+
return true
421+
}
422+
val metrics = MapLoader.getMetricsString(map.mapName)
423+
sender.sendMessage("${map.mapName} : ${metrics ?: "no metrics"}")
424+
return true
425+
}
426+
427+
else -> return false
428+
}
429+
}
320430
// -------------------------------------------------------------------------
321431
return false
322432
}

0 commit comments

Comments
 (0)