mirror of
				https://github.com/GayPizzaSpecifications/foundation.git
				synced 2025-11-04 11:39:39 +00:00 
			
		
		
		
	Gjallarhorn: Render pool rework and cleanup.
This commit is contained in:
		@ -63,13 +63,13 @@ class BlockChangeTimelapseCommand : CliktCommand("Block Change Timelapse", name
 | 
				
			|||||||
    val changelog = BlockChangelog.query(db, filter)
 | 
					    val changelog = BlockChangelog.query(db, filter)
 | 
				
			||||||
    logger.info("Block Changelog: ${changelog.changes.size} changes")
 | 
					    logger.info("Block Changelog: ${changelog.changes.size} changes")
 | 
				
			||||||
    val timelapse = BlockMapTimelapse<BufferedImage>()
 | 
					    val timelapse = BlockMapTimelapse<BufferedImage>()
 | 
				
			||||||
    var slices = timelapse.calculateChangelogSlices(changelog, timelapseMode.interval, timelapseIntervalLimit)
 | 
					    var slices = changelog.calculateChangelogSlices(timelapseMode.interval, timelapseIntervalLimit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (timelapseSpeedChangeThreshold != null && timelapseSpeedChangeMinimumIntervalSeconds != null) {
 | 
					    if (timelapseSpeedChangeThreshold != null && timelapseSpeedChangeMinimumIntervalSeconds != null) {
 | 
				
			||||||
      val minimumInterval = Duration.ofSeconds(timelapseSpeedChangeMinimumIntervalSeconds!!.toLong())
 | 
					      val minimumInterval = Duration.ofSeconds(timelapseSpeedChangeMinimumIntervalSeconds!!.toLong())
 | 
				
			||||||
      val blockChangeThreshold = timelapseSpeedChangeThreshold!!
 | 
					      val blockChangeThreshold = timelapseSpeedChangeThreshold!!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      slices = timelapse.splitChangelogSlicesWithThreshold(changelog, blockChangeThreshold, minimumInterval, slices)
 | 
					      slices = changelog.splitChangelogSlicesWithThreshold(blockChangeThreshold, minimumInterval, slices)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    logger.info("Timelapse Slices: ${slices.size} slices")
 | 
					    logger.info("Timelapse Slices: ${slices.size} slices")
 | 
				
			||||||
@ -80,7 +80,7 @@ class BlockChangeTimelapseCommand : CliktCommand("Block Change Timelapse", name
 | 
				
			|||||||
      changelog = changelog,
 | 
					      changelog = changelog,
 | 
				
			||||||
      blockTrackMode = if (considerAirBlocks) BlockTrackMode.AirOnDelete else BlockTrackMode.RemoveOnDelete,
 | 
					      blockTrackMode = if (considerAirBlocks) BlockTrackMode.AirOnDelete else BlockTrackMode.RemoveOnDelete,
 | 
				
			||||||
      delegate = timelapse,
 | 
					      delegate = timelapse,
 | 
				
			||||||
      rendererFactory = { expanse -> render.create(expanse) },
 | 
					      createRendererFunction = { expanse -> render.create(expanse) },
 | 
				
			||||||
      threadPoolExecutor = threadPoolExecutor
 | 
					      threadPoolExecutor = threadPoolExecutor
 | 
				
			||||||
    ) { slice, result ->
 | 
					    ) { slice, result ->
 | 
				
			||||||
      val speed = slice.relative.toSeconds().toDouble() / timelapseMode.interval.toSeconds().toDouble()
 | 
					      val speed = slice.relative.toSeconds().toDouble() / timelapseMode.interval.toSeconds().toDouble()
 | 
				
			||||||
 | 
				
			|||||||
@ -5,20 +5,56 @@ import org.jetbrains.exposed.sql.Database
 | 
				
			|||||||
import org.jetbrains.exposed.sql.Op
 | 
					import org.jetbrains.exposed.sql.Op
 | 
				
			||||||
import org.jetbrains.exposed.sql.select
 | 
					import org.jetbrains.exposed.sql.select
 | 
				
			||||||
import org.jetbrains.exposed.sql.transactions.transaction
 | 
					import org.jetbrains.exposed.sql.transactions.transaction
 | 
				
			||||||
 | 
					import java.time.Duration
 | 
				
			||||||
 | 
					import java.time.Instant
 | 
				
			||||||
 | 
					import java.util.stream.Stream
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BlockChangelog(
 | 
					class BlockChangelog(
 | 
				
			||||||
  val changes: List<BlockChange>
 | 
					  val changes: List<BlockChange>
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
  fun slice(slice: BlockChangelogSlice): BlockChangelog = BlockChangelog(changes.filter {
 | 
					  fun slice(slice: ChangelogSlice): BlockChangelog = BlockChangelog(changes.filter {
 | 
				
			||||||
    slice.isTimeWithin(it.time)
 | 
					    slice.isTimeWithin(it.time)
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fun countRelativeChangesInSlice(slice: BlockChangelogSlice): Int = changes.count {
 | 
					  fun countRelativeChangesInSlice(slice: ChangelogSlice): Int = changes.count {
 | 
				
			||||||
    slice.isRelativeWithin(it.time)
 | 
					    slice.isRelativeWithin(it.time)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  val changeTimeRange: BlockChangelogSlice
 | 
					  val changeTimeRange: ChangelogSlice
 | 
				
			||||||
    get() = BlockChangelogSlice(changes.minOf { it.time }, changes.maxOf { it.time })
 | 
					    get() = ChangelogSlice(changes.minOf { it.time }, changes.maxOf { it.time })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fun calculateChangelogSlices(interval: Duration, limit: Int? = null): List<ChangelogSlice> {
 | 
				
			||||||
 | 
					    val (start, end) = changeTimeRange
 | 
				
			||||||
 | 
					    var intervals = mutableListOf<Instant>()
 | 
				
			||||||
 | 
					    var current = start
 | 
				
			||||||
 | 
					    while (!current.isAfter(end)) {
 | 
				
			||||||
 | 
					      intervals.add(current)
 | 
				
			||||||
 | 
					      current = current.plus(interval)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (limit != null) {
 | 
				
			||||||
 | 
					      intervals = intervals.takeLast(limit).toMutableList()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return intervals.map { ChangelogSlice(start, it, interval) }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fun splitChangelogSlicesWithThreshold(
 | 
				
			||||||
 | 
					    targetChangeThreshold: Int,
 | 
				
			||||||
 | 
					    minimumTimeInterval: Duration,
 | 
				
			||||||
 | 
					    slices: List<ChangelogSlice>
 | 
				
			||||||
 | 
					  ): List<ChangelogSlice> {
 | 
				
			||||||
 | 
					    return slices.parallelStream().flatMap { slice ->
 | 
				
			||||||
 | 
					      val count = countRelativeChangesInSlice(slice)
 | 
				
			||||||
 | 
					      if (count < targetChangeThreshold ||
 | 
				
			||||||
 | 
					        slice.relative < minimumTimeInterval
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        return@flatMap Stream.of(slice)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      val split = slice.split()
 | 
				
			||||||
 | 
					      return@flatMap splitChangelogSlicesWithThreshold(targetChangeThreshold, minimumTimeInterval, split).parallelStream()
 | 
				
			||||||
 | 
					    }.toList()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  companion object {
 | 
					  companion object {
 | 
				
			||||||
    fun query(db: Database, filter: Op<Boolean> = Op.TRUE): BlockChangelog = transaction(db) {
 | 
					    fun query(db: Database, filter: Op<Boolean> = Op.TRUE): BlockChangelog = transaction(db) {
 | 
				
			||||||
 | 
				
			|||||||
@ -7,16 +7,16 @@ import java.util.concurrent.*
 | 
				
			|||||||
class BlockMapRenderPool<T>(
 | 
					class BlockMapRenderPool<T>(
 | 
				
			||||||
  val changelog: BlockChangelog,
 | 
					  val changelog: BlockChangelog,
 | 
				
			||||||
  val blockTrackMode: BlockTrackMode,
 | 
					  val blockTrackMode: BlockTrackMode,
 | 
				
			||||||
  val rendererFactory: (BlockExpanse) -> BlockMapRenderer<T>,
 | 
					  val createRendererFunction: (BlockExpanse) -> BlockMapRenderer<T>,
 | 
				
			||||||
  val delegate: RenderPoolDelegate<T>,
 | 
					  val delegate: BlockMapRenderPoolDelegate<T>,
 | 
				
			||||||
  val threadPoolExecutor: ThreadPoolExecutor,
 | 
					  val threadPoolExecutor: ThreadPoolExecutor,
 | 
				
			||||||
  val renderResultCallback: (BlockChangelogSlice, T) -> Unit
 | 
					  val renderResultCallback: (ChangelogSlice, T) -> Unit
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
  private val trackers = ConcurrentHashMap<BlockChangelogSlice, BlockLogTracker>()
 | 
					  private val trackers = ConcurrentHashMap<ChangelogSlice, BlockLogTracker>()
 | 
				
			||||||
  private val playbackJobFutures = ConcurrentHashMap<BlockChangelogSlice, Future<*>>()
 | 
					  private val playbackJobFutures = ConcurrentHashMap<ChangelogSlice, Future<*>>()
 | 
				
			||||||
  private val renderJobFutures = ConcurrentHashMap<BlockChangelogSlice, Future<*>>()
 | 
					  private val renderJobFutures = ConcurrentHashMap<ChangelogSlice, Future<*>>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fun submitPlaybackJob(id: String, slice: BlockChangelogSlice) {
 | 
					  fun submitPlaybackJob(id: String, slice: ChangelogSlice) {
 | 
				
			||||||
    val future = threadPoolExecutor.submit {
 | 
					    val future = threadPoolExecutor.submit {
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        runPlaybackSlice(id, slice)
 | 
					        runPlaybackSlice(id, slice)
 | 
				
			||||||
@ -27,7 +27,7 @@ class BlockMapRenderPool<T>(
 | 
				
			|||||||
    playbackJobFutures[slice] = future
 | 
					    playbackJobFutures[slice] = future
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fun submitRenderJob(slice: BlockChangelogSlice, callback: () -> T) {
 | 
					  fun submitRenderJob(slice: ChangelogSlice, callback: () -> T) {
 | 
				
			||||||
    val future = threadPoolExecutor.submit {
 | 
					    val future = threadPoolExecutor.submit {
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        val result = callback()
 | 
					        val result = callback()
 | 
				
			||||||
@ -39,7 +39,7 @@ class BlockMapRenderPool<T>(
 | 
				
			|||||||
    renderJobFutures[slice] = future
 | 
					    renderJobFutures[slice] = future
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fun render(slices: List<BlockChangelogSlice>) {
 | 
					  fun render(slices: List<ChangelogSlice>) {
 | 
				
			||||||
    for (slice in slices) {
 | 
					    for (slice in slices) {
 | 
				
			||||||
      submitPlaybackJob((slices.indexOf(slice) + 1).toString(), slice)
 | 
					      submitPlaybackJob((slices.indexOf(slice) + 1).toString(), slice)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -48,30 +48,27 @@ class BlockMapRenderPool<T>(
 | 
				
			|||||||
      future.get()
 | 
					      future.get()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    delegate.buildRenderJobs(this, trackers)
 | 
					    delegate.onAllPlaybackComplete(this, trackers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (future in renderJobFutures.values) {
 | 
					    for (future in renderJobFutures.values) {
 | 
				
			||||||
      future.get()
 | 
					      future.get()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private fun runPlaybackSlice(id: String, slice: BlockChangelogSlice) {
 | 
					  private fun runPlaybackSlice(id: String, slice: ChangelogSlice) {
 | 
				
			||||||
    val start = System.currentTimeMillis()
 | 
					    val start = System.currentTimeMillis()
 | 
				
			||||||
    val sliced = changelog.slice(slice)
 | 
					    val sliced = changelog.slice(slice)
 | 
				
			||||||
    val tracker = BlockLogTracker(blockTrackMode)
 | 
					    val tracker = BlockLogTracker(blockTrackMode)
 | 
				
			||||||
    tracker.replay(sliced)
 | 
					    tracker.replay(sliced)
 | 
				
			||||||
    if (tracker.isNotEmpty()) {
 | 
					    if (tracker.isNotEmpty()) {
 | 
				
			||||||
      trackers[slice] = tracker
 | 
					      trackers[slice] = tracker
 | 
				
			||||||
 | 
					      delegate.onSinglePlaybackComplete(this, slice, tracker)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    val end = System.currentTimeMillis()
 | 
					    val end = System.currentTimeMillis()
 | 
				
			||||||
    val timeInMilliseconds = end - start
 | 
					    val timeInMilliseconds = end - start
 | 
				
			||||||
    logger.debug("Playback Completed for Slice $id in ${timeInMilliseconds}ms")
 | 
					    logger.debug("Playback Completed for Slice $id in ${timeInMilliseconds}ms")
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  interface RenderPoolDelegate<T> {
 | 
					 | 
				
			||||||
    fun buildRenderJobs(pool: BlockMapRenderPool<T>, trackers: MutableMap<BlockChangelogSlice, BlockLogTracker>)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  companion object {
 | 
					  companion object {
 | 
				
			||||||
    private val logger = LoggerFactory.getLogger(BlockMapRenderPool::class.java)
 | 
					    private val logger = LoggerFactory.getLogger(BlockMapRenderPool::class.java)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					package cloud.kubelet.foundation.gjallarhorn.state
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface BlockMapRenderPoolDelegate<T> {
 | 
				
			||||||
 | 
					  fun onSinglePlaybackComplete(pool: BlockMapRenderPool<T>, slice: ChangelogSlice, tracker: BlockLogTracker)
 | 
				
			||||||
 | 
					  fun onAllPlaybackComplete(pool: BlockMapRenderPool<T>, trackers: Map<ChangelogSlice, BlockLogTracker>)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,50 +1,14 @@
 | 
				
			|||||||
package cloud.kubelet.foundation.gjallarhorn.state
 | 
					package cloud.kubelet.foundation.gjallarhorn.state
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.time.Duration
 | 
					 | 
				
			||||||
import java.time.Instant
 | 
					 | 
				
			||||||
import java.util.stream.Stream
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class BlockMapTimelapse<T> :
 | 
					class BlockMapTimelapse<T> :
 | 
				
			||||||
  BlockMapRenderPool.RenderPoolDelegate<T> {
 | 
					  BlockMapRenderPoolDelegate<T> {
 | 
				
			||||||
  fun calculateChangelogSlices(
 | 
					  override fun onSinglePlaybackComplete(pool: BlockMapRenderPool<T>, slice: ChangelogSlice, tracker: BlockLogTracker) {
 | 
				
			||||||
    changelog: BlockChangelog, interval: Duration, limit: Int? = null
 | 
					    throw UnsupportedOperationException()
 | 
				
			||||||
  ): List<BlockChangelogSlice> {
 | 
					 | 
				
			||||||
    val (start, end) = changelog.changeTimeRange
 | 
					 | 
				
			||||||
    var intervals = mutableListOf<Instant>()
 | 
					 | 
				
			||||||
    var current = start
 | 
					 | 
				
			||||||
    while (!current.isAfter(end)) {
 | 
					 | 
				
			||||||
      intervals.add(current)
 | 
					 | 
				
			||||||
      current = current.plus(interval)
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (limit != null) {
 | 
					  override fun onAllPlaybackComplete(
 | 
				
			||||||
      intervals = intervals.takeLast(limit).toMutableList()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return intervals.map { BlockChangelogSlice(start, it, interval) }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  fun splitChangelogSlicesWithThreshold(
 | 
					 | 
				
			||||||
    changelog: BlockChangelog,
 | 
					 | 
				
			||||||
    targetChangeThreshold: Int,
 | 
					 | 
				
			||||||
    minimumTimeInterval: Duration,
 | 
					 | 
				
			||||||
    slices: List<BlockChangelogSlice>
 | 
					 | 
				
			||||||
  ): List<BlockChangelogSlice> {
 | 
					 | 
				
			||||||
    return slices.parallelStream().flatMap { slice ->
 | 
					 | 
				
			||||||
      val count = changelog.countRelativeChangesInSlice(slice)
 | 
					 | 
				
			||||||
      if (count < targetChangeThreshold ||
 | 
					 | 
				
			||||||
        slice.relative < minimumTimeInterval
 | 
					 | 
				
			||||||
      ) {
 | 
					 | 
				
			||||||
        return@flatMap Stream.of(slice)
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      val split = slice.split()
 | 
					 | 
				
			||||||
      return@flatMap splitChangelogSlicesWithThreshold(changelog, targetChangeThreshold, minimumTimeInterval, split).parallelStream()
 | 
					 | 
				
			||||||
    }.toList()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  override fun buildRenderJobs(
 | 
					 | 
				
			||||||
    pool: BlockMapRenderPool<T>,
 | 
					    pool: BlockMapRenderPool<T>,
 | 
				
			||||||
    trackers: MutableMap<BlockChangelogSlice, BlockLogTracker>
 | 
					    trackers: Map<ChangelogSlice, BlockLogTracker>
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    if (trackers.isEmpty()) {
 | 
					    if (trackers.isEmpty()) {
 | 
				
			||||||
      return
 | 
					      return
 | 
				
			||||||
@ -56,7 +20,7 @@ class BlockMapTimelapse<T> :
 | 
				
			|||||||
    val globalBlockMax = BlockCoordinate.maxOf(allBlockMaxes)
 | 
					    val globalBlockMax = BlockCoordinate.maxOf(allBlockMaxes)
 | 
				
			||||||
    val globalBlockExpanse = BlockExpanse.offsetAndMax(globalBlockOffset, globalBlockMax)
 | 
					    val globalBlockExpanse = BlockExpanse.offsetAndMax(globalBlockOffset, globalBlockMax)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val renderer = pool.rendererFactory(globalBlockExpanse)
 | 
					    val renderer = pool.createRendererFunction(globalBlockExpanse)
 | 
				
			||||||
    for ((slice, tracker) in trackers) {
 | 
					    for ((slice, tracker) in trackers) {
 | 
				
			||||||
      pool.submitRenderJob(slice) {
 | 
					      pool.submitRenderJob(slice) {
 | 
				
			||||||
        val map = tracker.buildBlockMap(globalBlockExpanse.offset)
 | 
					        val map = tracker.buildBlockMap(globalBlockExpanse.offset)
 | 
				
			||||||
 | 
				
			|||||||
@ -3,21 +3,21 @@ package cloud.kubelet.foundation.gjallarhorn.state
 | 
				
			|||||||
import java.time.Duration
 | 
					import java.time.Duration
 | 
				
			||||||
import java.time.Instant
 | 
					import java.time.Instant
 | 
				
			||||||
 | 
					
 | 
				
			||||||
data class BlockChangelogSlice(val from: Instant, val to: Instant, val relative: Duration) {
 | 
					data class ChangelogSlice(val from: Instant, val to: Instant, val relative: Duration) {
 | 
				
			||||||
  constructor(from: Instant, to: Instant) : this(from, to, Duration.ofMillis(to.toEpochMilli() - from.toEpochMilli()))
 | 
					  constructor(from: Instant, to: Instant) : this(from, to, Duration.ofMillis(to.toEpochMilli() - from.toEpochMilli()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  val changeResolutionTime: Instant = to.minus(relative)
 | 
					  val relativeChangeStart: Instant = to.minus(relative)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fun isTimeWithin(time: Instant) = time in from..to
 | 
					  fun isTimeWithin(time: Instant) = time in from..to
 | 
				
			||||||
  fun isRelativeWithin(time: Instant) = time in changeResolutionTime..to
 | 
					  fun isRelativeWithin(time: Instant) = time in relativeChangeStart..to
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fun split(): List<BlockChangelogSlice> {
 | 
					  fun split(): List<ChangelogSlice> {
 | 
				
			||||||
    val half = relative.dividedBy(2)
 | 
					    val half = relative.dividedBy(2)
 | 
				
			||||||
    val initial = to.minus(relative)
 | 
					    val initial = to.minus(relative)
 | 
				
			||||||
    val first = initial.plus(half)
 | 
					    val first = initial.plus(half)
 | 
				
			||||||
    return listOf(
 | 
					    return listOf(
 | 
				
			||||||
      BlockChangelogSlice(from, first, half),
 | 
					      ChangelogSlice(from, first, half),
 | 
				
			||||||
      BlockChangelogSlice(from, to, half)
 | 
					      ChangelogSlice(from, to, half)
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user