fix(stage-ui-three): memory leak & should update hook issue

This commit is contained in:
Neko Ayaka
2026-01-09 18:11:23 +08:00
parent a0700303d8
commit 222e776beb
4 changed files with 18 additions and 11 deletions
@@ -165,7 +165,7 @@ const beatSync = createBeatSyncController({
})
// Listen for model reload requests (e.g., when runtime motion is uploaded)
live2dStore.onShouldUpdateView(() => {
const disposeShouldUpdateView = live2dStore.onShouldUpdateView(() => {
loadModel()
})
@@ -620,6 +620,7 @@ onMounted(async () => {
onUnmounted(() => {
isUnmounted = true
disposeShouldUpdateView?.()
})
function listMotionGroups() {
@@ -36,10 +36,13 @@ export const defaultModelParameters = {
export const useLive2d = defineStore('live2d', () => {
const { post, data } = useBroadcastChannel<BroadcastChannelEvents, BroadcastChannelEvents>({ name: 'airi-stores-live2d' })
const shouldUpdateViewHooks = ref<Array<() => void>>([])
const shouldUpdateViewHooks = ref(new Set<() => void>())
const onShouldUpdateView = (hook: () => void) => {
shouldUpdateViewHooks.value.push(hook)
shouldUpdateViewHooks.value.add(hook)
return () => {
shouldUpdateViewHooks.value.delete(hook)
}
}
function shouldUpdateView() {
@@ -61,10 +61,13 @@ interface BroadcastChannelEventShouldUpdateView {
export const useModelStore = defineStore('modelStore', () => {
const { post, data } = useBroadcastChannel<BroadcastChannelEvents, BroadcastChannelEvents>({ name: 'airi-stores-live2d' })
const shouldUpdateViewHooks = ref<Array<() => void>>([])
const shouldUpdateViewHooks = ref(new Set<() => void>())
const onShouldUpdateView = (hook: () => void) => {
shouldUpdateViewHooks.value.push(hook)
shouldUpdateViewHooks.value.add(hook)
return () => {
shouldUpdateViewHooks.value.delete(hook)
}
}
function shouldUpdateView() {
@@ -101,6 +101,7 @@ const live2dStore = useLive2d()
const vrmStore = useModelStore()
const showStage = ref(true)
const viewUpdateCleanups: Array<() => void> = []
// Caption + Presentation broadcast channels
type CaptionChannelEvent
@@ -114,23 +115,21 @@ type PresentEvent
| { type: 'assistant-append', text: string }
const { post: postPresent } = useBroadcastChannel<PresentEvent, PresentEvent>({ name: 'airi-chat-present' })
// TODO: duplicate calls may happen if this component mounted multiple times
live2dStore.onShouldUpdateView(async () => {
viewUpdateCleanups.push(live2dStore.onShouldUpdateView(async () => {
showStage.value = false
await settingsStore.updateStageModel()
setTimeout(() => {
showStage.value = true
}, 100)
})
}))
// TODO: duplicate calls may happen if this component mounted multiple times
vrmStore.onShouldUpdateView(async () => {
viewUpdateCleanups.push(vrmStore.onShouldUpdateView(async () => {
showStage.value = false
await settingsStore.updateStageModel()
setTimeout(() => {
showStage.value = true
}, 100)
})
}))
const audioAnalyser = ref<AnalyserNode>()
const nowSpeaking = ref(false)
@@ -353,6 +352,7 @@ onUnmounted(() => {
}
chatHookCleanups.forEach(dispose => dispose?.())
viewUpdateCleanups.forEach(dispose => dispose?.())
})
defineExpose({