mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-04 22:58:29 +08:00
Improved AssemblyWorker performance.
This commit is contained in:
parent
955d7dad29
commit
f4d39afdee
@ -78,7 +78,7 @@ interface BasicBlock {
|
||||
/**
|
||||
* Graph representing the flow of control through the [BasicBlock]s of a script.
|
||||
*/
|
||||
class ControlFlowGraph(
|
||||
class ControlFlowGraph internal constructor(
|
||||
val blocks: List<BasicBlock>,
|
||||
private val instructionToBlock: Map<Instruction, BasicBlock>,
|
||||
) {
|
||||
|
@ -15,8 +15,11 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
val kotlinLoggingVersion: String by project.extra
|
||||
|
||||
dependencies {
|
||||
api(project(":web:shared"))
|
||||
implementation("io.github.microutils:kotlin-logging-js:$kotlinLoggingVersion")
|
||||
|
||||
testImplementation(kotlin("test-js"))
|
||||
testImplementation(project(":test-utils"))
|
||||
|
@ -1,16 +1,23 @@
|
||||
package world.phantasmal.web.assemblyWorker
|
||||
|
||||
import mu.KotlinLogging
|
||||
import world.phantasmal.core.*
|
||||
import world.phantasmal.lib.asm.*
|
||||
import world.phantasmal.lib.asm.dataFlowAnalysis.ControlFlowGraph
|
||||
import world.phantasmal.lib.asm.dataFlowAnalysis.getMapDesignations
|
||||
import world.phantasmal.lib.asm.dataFlowAnalysis.getStackValue
|
||||
import world.phantasmal.web.shared.*
|
||||
import world.phantasmal.web.shared.Throttle
|
||||
import world.phantasmal.web.shared.messages.*
|
||||
import kotlin.math.min
|
||||
import kotlin.time.measureTime
|
||||
import world.phantasmal.lib.asm.AssemblyProblem as AssemblerAssemblyProblem
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
class AssemblyWorker(private val sendMessage: (ServerMessage) -> Unit) {
|
||||
private val messageQueue: MutableList<ClientMessage> = mutableListOf()
|
||||
private val messageProcessingThrottle = Throttle(wait = 100)
|
||||
|
||||
// User input.
|
||||
private var inlineStackArgs: Boolean = true
|
||||
private val asm: JsArray<String> = jsArrayOf()
|
||||
@ -29,28 +36,91 @@ class AssemblyWorker(private val sendMessage: (ServerMessage) -> Unit) {
|
||||
|
||||
private var mapDesignations: Map<Int, Int>? = null
|
||||
|
||||
fun receiveMessage(message: ClientMessage) =
|
||||
when (message) {
|
||||
is ClientNotification.SetAsm ->
|
||||
setAsm(message.asm, message.inlineStackArgs)
|
||||
is ClientNotification.UpdateAsm ->
|
||||
updateAsm(message.changes)
|
||||
is Request.GetCompletions ->
|
||||
getCompletions(message.id, message.lineNo, message.col)
|
||||
is Request.GetSignatureHelp ->
|
||||
getSignatureHelp(message.id, message.lineNo, message.col)
|
||||
is Request.GetHover ->
|
||||
getHover(message.id, message.lineNo, message.col)
|
||||
is Request.GetDefinition ->
|
||||
getDefinition(message.id, message.lineNo, message.col)
|
||||
fun receiveMessage(message: ClientMessage) {
|
||||
messageQueue.add(message)
|
||||
messageProcessingThrottle(::processMessages)
|
||||
}
|
||||
|
||||
private fun processMessages() {
|
||||
// Split messages into ASM changes and other messages. Remove useless/duplicate
|
||||
// notifications.
|
||||
val asmChanges = mutableListOf<ClientNotification>()
|
||||
val otherMessages = mutableListOf<ClientMessage>()
|
||||
|
||||
for (message in messageQueue) {
|
||||
when (message) {
|
||||
is ClientNotification.SetAsm -> {
|
||||
// All previous ASM change messages can be discarded when the entire ASM has
|
||||
// changed.
|
||||
asmChanges.clear()
|
||||
asmChanges.add(message)
|
||||
}
|
||||
|
||||
is ClientNotification.UpdateAsm ->
|
||||
asmChanges.add(message)
|
||||
|
||||
else ->
|
||||
otherMessages.add(message)
|
||||
}
|
||||
}
|
||||
|
||||
messageQueue.clear()
|
||||
|
||||
// Process ASM changes first.
|
||||
processAsmChanges(asmChanges)
|
||||
otherMessages.forEach(::processMessage)
|
||||
}
|
||||
|
||||
private fun processAsmChanges(messages: List<ClientNotification>) {
|
||||
if (messages.isNotEmpty()) {
|
||||
val time = measureTime {
|
||||
for (message in messages) {
|
||||
when (message) {
|
||||
is ClientNotification.SetAsm ->
|
||||
setAsm(message.asm, message.inlineStackArgs)
|
||||
|
||||
is ClientNotification.UpdateAsm ->
|
||||
updateAsm(message.changes)
|
||||
}
|
||||
}
|
||||
|
||||
processAsm()
|
||||
}
|
||||
|
||||
logger.trace {
|
||||
"Processed ${messages.size} assembly changes in ${time.inMilliseconds}ms."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processMessage(message: ClientMessage) {
|
||||
val time = measureTime {
|
||||
when (message) {
|
||||
is ClientNotification.SetAsm,
|
||||
is ClientNotification.UpdateAsm ->
|
||||
logger.error { "Unexpected ${message::class.simpleName}." }
|
||||
|
||||
is Request.GetCompletions ->
|
||||
getCompletions(message.id, message.lineNo, message.col)
|
||||
|
||||
is Request.GetSignatureHelp ->
|
||||
getSignatureHelp(message.id, message.lineNo, message.col)
|
||||
|
||||
is Request.GetHover ->
|
||||
getHover(message.id, message.lineNo, message.col)
|
||||
|
||||
is Request.GetDefinition ->
|
||||
getDefinition(message.id, message.lineNo, message.col)
|
||||
}
|
||||
}
|
||||
|
||||
logger.trace { "Processed ${message::class.simpleName} in ${time.inMilliseconds}ms." }
|
||||
}
|
||||
|
||||
private fun setAsm(asm: List<String>, inlineStackArgs: Boolean) {
|
||||
this.inlineStackArgs = inlineStackArgs
|
||||
this.asm.splice(0, this.asm.length, *asm.toTypedArray())
|
||||
mapDesignations = null
|
||||
|
||||
processAsm()
|
||||
}
|
||||
|
||||
private fun updateAsm(changes: List<AsmChange>) {
|
||||
@ -91,8 +161,6 @@ class AssemblyWorker(private val sendMessage: (ServerMessage) -> Unit) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processAsm()
|
||||
}
|
||||
|
||||
private fun replaceLinePart(
|
||||
@ -176,9 +244,11 @@ class AssemblyWorker(private val sendMessage: (ServerMessage) -> Unit) {
|
||||
|
||||
if (designations != mapDesignations) {
|
||||
mapDesignations = designations
|
||||
sendMessage(ServerNotification.MapDesignations(
|
||||
designations
|
||||
))
|
||||
sendMessage(
|
||||
ServerNotification.MapDesignations(
|
||||
designations
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,21 @@ package world.phantasmal.web.assemblyWorker
|
||||
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import org.w3c.dom.DedicatedWorkerGlobalScope
|
||||
import mu.KotlinLoggingConfiguration
|
||||
import mu.KotlinLoggingLevel
|
||||
import world.phantasmal.web.shared.JSON_FORMAT
|
||||
|
||||
external val self: DedicatedWorkerGlobalScope
|
||||
import world.phantasmal.web.shared.externals.self
|
||||
import world.phantasmal.web.shared.logging.LogAppender
|
||||
import world.phantasmal.web.shared.logging.LogFormatter
|
||||
|
||||
fun main() {
|
||||
KotlinLoggingConfiguration.FORMATTER = LogFormatter()
|
||||
KotlinLoggingConfiguration.APPENDER = LogAppender()
|
||||
|
||||
if (self.location.hostname == "localhost") {
|
||||
KotlinLoggingConfiguration.LOG_LEVEL = KotlinLoggingLevel.TRACE
|
||||
}
|
||||
|
||||
val asmWorker = AssemblyWorker(
|
||||
sendMessage = { message ->
|
||||
self.postMessage(JSON_FORMAT.encodeToString(message))
|
||||
|
@ -0,0 +1,38 @@
|
||||
package world.phantasmal.web.shared
|
||||
|
||||
import world.phantasmal.web.shared.externals.self
|
||||
|
||||
/**
|
||||
* Helper for limiting the amount of times a function is called within a given window.
|
||||
*
|
||||
* @param wait The number of milliseconds to throttle invocations to.
|
||||
* @param leading Invoke on the leading edge of the timeout window.
|
||||
* @param trailing Invoke on the trailing edge of the timeout window.
|
||||
*/
|
||||
class Throttle(
|
||||
private val wait: Int,
|
||||
private val leading: Boolean = true,
|
||||
private val trailing: Boolean = true,
|
||||
) {
|
||||
private var timeout: Int? = null
|
||||
private var invokeOnTimeout = false
|
||||
|
||||
operator fun invoke(f: () -> Unit) {
|
||||
if (timeout == null) {
|
||||
if (leading) {
|
||||
f()
|
||||
}
|
||||
|
||||
timeout = self.setTimeout({
|
||||
if (invokeOnTimeout) {
|
||||
f()
|
||||
}
|
||||
|
||||
timeout = null
|
||||
invokeOnTimeout = false
|
||||
}, wait)
|
||||
} else {
|
||||
invokeOnTimeout = trailing
|
||||
}
|
||||
}
|
||||
}
|
5
web/shared/src/jsMain/kotlin/world/phantasmal/web/shared/externals/Self.kt
vendored
Normal file
5
web/shared/src/jsMain/kotlin/world/phantasmal/web/shared/externals/Self.kt
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
package world.phantasmal.web.shared.externals
|
||||
|
||||
import org.w3c.dom.DedicatedWorkerGlobalScope
|
||||
|
||||
external val self: DedicatedWorkerGlobalScope
|
@ -1,4 +1,4 @@
|
||||
package world.phantasmal.web.core.logging
|
||||
package world.phantasmal.web.shared.logging
|
||||
|
||||
import mu.Appender
|
||||
|
@ -1,4 +1,4 @@
|
||||
package world.phantasmal.web.core.logging
|
||||
package world.phantasmal.web.shared.logging
|
||||
|
||||
import mu.Formatter
|
||||
import mu.KotlinLoggingLevel
|
@ -1,4 +1,4 @@
|
||||
package world.phantasmal.web.core.logging
|
||||
package world.phantasmal.web.shared.logging
|
||||
|
||||
class MessageWithThrowable(
|
||||
val message: Any?,
|
@ -18,12 +18,12 @@ import world.phantasmal.core.disposable.disposable
|
||||
import world.phantasmal.observable.value.mutableVal
|
||||
import world.phantasmal.web.application.Application
|
||||
import world.phantasmal.web.core.loading.AssetLoader
|
||||
import world.phantasmal.web.core.logging.LogAppender
|
||||
import world.phantasmal.web.core.logging.LogFormatter
|
||||
import world.phantasmal.web.core.rendering.DisposableThreeRenderer
|
||||
import world.phantasmal.web.core.stores.ApplicationUrl
|
||||
import world.phantasmal.web.externals.three.WebGLRenderer
|
||||
import world.phantasmal.web.shared.JSON_FORMAT
|
||||
import world.phantasmal.web.shared.logging.LogAppender
|
||||
import world.phantasmal.web.shared.logging.LogFormatter
|
||||
import world.phantasmal.webui.dom.disposableListener
|
||||
import world.phantasmal.webui.dom.root
|
||||
import world.phantasmal.webui.obj
|
||||
|
@ -1,24 +0,0 @@
|
||||
package world.phantasmal.web.core
|
||||
|
||||
import kotlinx.browser.window
|
||||
|
||||
/**
|
||||
* Helper for limiting the amount of times a function is called within a given time frame.
|
||||
*
|
||||
* @param before the amount of time in ms before the function is actually called. E.g., if [before]
|
||||
* is 10 and [invoke] is called once, the given function won't be called until 10ms have passed. If
|
||||
* invoke is called again before the 10ms have passed, it will still only be called once after 10ms
|
||||
* have passed since the first call to invoke.
|
||||
*/
|
||||
class Throttle(private val before: Int) {
|
||||
private var timeout: Int = -1
|
||||
|
||||
operator fun invoke(f: () -> Unit) {
|
||||
if (timeout == -1) {
|
||||
timeout = window.setTimeout({
|
||||
f()
|
||||
timeout = -1
|
||||
}, before)
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ import world.phantasmal.core.math.degToRad
|
||||
import world.phantasmal.lib.fileFormats.ninja.NinjaObject
|
||||
import world.phantasmal.lib.fileFormats.ninja.NjMotion
|
||||
import world.phantasmal.lib.fileFormats.ninja.NjObject
|
||||
import world.phantasmal.web.core.Throttle
|
||||
import world.phantasmal.web.core.boundingSphere
|
||||
import world.phantasmal.web.core.isSkinnedMesh
|
||||
import world.phantasmal.web.core.rendering.*
|
||||
@ -14,6 +13,7 @@ import world.phantasmal.web.core.rendering.Renderer
|
||||
import world.phantasmal.web.core.rendering.conversion.*
|
||||
import world.phantasmal.web.core.times
|
||||
import world.phantasmal.web.externals.three.*
|
||||
import world.phantasmal.web.shared.Throttle
|
||||
import world.phantasmal.web.viewer.stores.NinjaGeometry
|
||||
import world.phantasmal.web.viewer.stores.ViewerStore
|
||||
import kotlin.math.roundToInt
|
||||
@ -24,7 +24,7 @@ class MeshRenderer(
|
||||
createThreeRenderer: (HTMLCanvasElement) -> DisposableThreeRenderer,
|
||||
) : Renderer() {
|
||||
private val clock = Clock()
|
||||
private val throttleRebuildMesh = Throttle(before = 10)
|
||||
private val throttleRebuildMesh = Throttle(wait = 10, leading = false, trailing = true)
|
||||
|
||||
private var obj3d: Object3D? = null
|
||||
private var skeletonHelper: SkeletonHelper? = null
|
||||
@ -32,24 +32,28 @@ class MeshRenderer(
|
||||
private var updateAnimationTime = true
|
||||
private var charClassActive = false
|
||||
|
||||
override val context = addDisposable(RenderContext(
|
||||
createCanvas(),
|
||||
PerspectiveCamera(
|
||||
fov = 45.0,
|
||||
aspect = 1.0,
|
||||
near = 10.0,
|
||||
far = 5_000.0,
|
||||
override val context = addDisposable(
|
||||
RenderContext(
|
||||
createCanvas(),
|
||||
PerspectiveCamera(
|
||||
fov = 45.0,
|
||||
aspect = 1.0,
|
||||
near = 10.0,
|
||||
far = 5_000.0,
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
override val threeRenderer = addDisposable(createThreeRenderer(context.canvas)).renderer
|
||||
|
||||
override val inputManager = addDisposable(OrbitalCameraInputManager(
|
||||
context.canvas,
|
||||
context.camera,
|
||||
position = Vector3(.0, .0, .0),
|
||||
screenSpacePanning = true,
|
||||
))
|
||||
override val inputManager = addDisposable(
|
||||
OrbitalCameraInputManager(
|
||||
context.canvas,
|
||||
context.camera,
|
||||
position = Vector3(.0, .0, .0),
|
||||
screenSpacePanning = true,
|
||||
)
|
||||
)
|
||||
|
||||
init {
|
||||
observe(viewerStore.currentNinjaGeometry) { rebuildMesh(resetCamera = true) }
|
||||
@ -124,8 +128,10 @@ class MeshRenderer(
|
||||
}
|
||||
}
|
||||
|
||||
is NinjaGeometry.Render -> renderGeometryToGroup(ninjaGeometry.geometry,
|
||||
textures)
|
||||
is NinjaGeometry.Render -> renderGeometryToGroup(
|
||||
ninjaGeometry.geometry,
|
||||
textures
|
||||
)
|
||||
|
||||
is NinjaGeometry.Collision -> collisionGeometryToGroup(ninjaGeometry.geometry)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user